From 53d39dd36289eb6227856504727290bd76b8cdd6 Mon Sep 17 00:00:00 2001 From: Ralf Haferkamp Date: Mon, 14 Oct 2024 15:16:48 +0200 Subject: [PATCH 01/11] refactor(thumbnails): merge processor into generator Processor is now part of the generator. This should make it possible to implement thumbnail generators that do not depend on the 'imaging' module. --- .../thumbnails/pkg/thumbnail/generator.go | 54 +++++++++++++++---- .../thumbnails/pkg/thumbnail/thumbnail.go | 12 ++--- .../pkg/thumbnail/thumbnail_test.go | 14 +++-- 3 files changed, 55 insertions(+), 25 deletions(-) diff --git a/services/thumbnails/pkg/thumbnail/generator.go b/services/thumbnails/pkg/thumbnail/generator.go index 177aad17a47..463daddcead 100644 --- a/services/thumbnails/pkg/thumbnail/generator.go +++ b/services/thumbnails/pkg/thumbnail/generator.go @@ -13,27 +13,59 @@ import ( // Generator generates a web friendly file version. type Generator interface { - Generate(size image.Rectangle, img interface{}, processor Processor) (interface{}, error) + Generate(size image.Rectangle, img interface{}) (interface{}, error) + ProcessorID() string } // SimpleGenerator is the default image generator and is used for all image types expect gif. -type SimpleGenerator struct{} +type SimpleGenerator struct { + processor Processor +} + +func NewSimpleGenerator(filetype, process string) (SimpleGenerator, error) { + processor, err := ProcessorFor(filetype, process) + if err != nil { + return SimpleGenerator{}, err + } + return SimpleGenerator{processor: processor}, nil +} + +// ProcessorID returns the processor identification. +func (g SimpleGenerator) ProcessorID() string { + return g.processor.ID() +} // Generate generates a alternative image version. -func (g SimpleGenerator) Generate(size image.Rectangle, img interface{}, processor Processor) (interface{}, error) { +func (g SimpleGenerator) Generate(size image.Rectangle, img interface{}) (interface{}, error) { m, ok := img.(image.Image) if !ok { return nil, errors.ErrInvalidType } - return processor.Process(m, size.Dx(), size.Dy(), imaging.Lanczos), nil + return g.processor.Process(m, size.Dx(), size.Dy(), imaging.Lanczos), nil } // GifGenerator is used to create a web friendly version of the provided gif image. -type GifGenerator struct{} +type GifGenerator struct { + processor Processor +} + +func NewGifGenerator(filetype, process string) (GifGenerator, error) { + processor, err := ProcessorFor(filetype, process) + if err != nil { + return GifGenerator{}, err + } + return GifGenerator{processor: processor}, nil + +} + +// ProcessorID returns the processor identification. +func (g GifGenerator) ProcessorID() string { + return g.processor.ID() +} // Generate generates a alternative gif version. -func (g GifGenerator) Generate(size image.Rectangle, img interface{}, processor Processor) (interface{}, error) { +func (g GifGenerator) Generate(size image.Rectangle, img interface{}) (interface{}, error) { // Code inspired by https://github.com/willnorris/gifresize/blob/db93a7e1dcb1c279f7eeb99cc6d90b9e2e23e871/gifresize.go m, ok := img.(*gif.GIF) @@ -49,7 +81,7 @@ func (g GifGenerator) Generate(size image.Rectangle, img interface{}, processor bounds := frame.Bounds() prev := tmp draw.Draw(tmp, bounds, frame, bounds.Min, draw.Over) - processed := processor.Process(tmp, size.Dx(), size.Dy(), imaging.Lanczos) + processed := g.processor.Process(tmp, size.Dx(), size.Dy(), imaging.Lanczos) m.Image[i] = g.imageToPaletted(processed, frame.Palette) switch m.Disposal[i] { @@ -72,14 +104,14 @@ func (g GifGenerator) imageToPaletted(img image.Image, p color.Palette) *image.P return pm } -// GeneratorForType returns the generator for a given file type +// GeneratorFor returns the generator for a given file type // or nil if the type is not supported. -func GeneratorForType(fileType string) (Generator, error) { +func GeneratorFor(fileType, processorID string) (Generator, error) { switch strings.ToLower(fileType) { case typePng, typeJpg, typeJpeg, typeGgs: - return SimpleGenerator{}, nil + return NewSimpleGenerator(fileType, processorID) case typeGif: - return GifGenerator{}, nil + return NewGifGenerator(fileType, processorID) default: return nil, errors.ErrNoEncoderForType } diff --git a/services/thumbnails/pkg/thumbnail/thumbnail.go b/services/thumbnails/pkg/thumbnail/thumbnail.go index 48ffab36bd8..adbb13d6c46 100644 --- a/services/thumbnails/pkg/thumbnail/thumbnail.go +++ b/services/thumbnails/pkg/thumbnail/thumbnail.go @@ -35,7 +35,6 @@ type Request struct { Encoder Encoder Generator Generator Checksum string - Processor Processor } // Manager is responsible for generating thumbnails @@ -86,7 +85,7 @@ func (s SimpleManager) Generate(r Request, img interface{}) (string, error) { return "", errors.ErrImageTooLarge } - thumbnail, err := r.Generator.Generate(match, img, r.Processor) + thumbnail, err := r.Generator.Generate(match, img) if err != nil { return "", err } @@ -120,7 +119,7 @@ func mapToStorageRequest(r Request) storage.Request { Checksum: r.Checksum, Resolution: r.Resolution, Types: r.Encoder.Types(), - Characteristic: r.Processor.ID(), + Characteristic: r.Generator.ProcessorID(), } } @@ -136,7 +135,7 @@ func IsMimeTypeSupported(m string) bool { // PrepareRequest prepare the request based on image parameters func PrepareRequest(width, height int, tType, checksum, pID string) (Request, error) { - generator, err := GeneratorForType(tType) + generator, err := GeneratorFor(tType, pID) if err != nil { return Request{}, err } @@ -144,16 +143,11 @@ func PrepareRequest(width, height int, tType, checksum, pID string) (Request, er if err != nil { return Request{}, err } - processor, err := ProcessorFor(pID, tType) - if err != nil { - return Request{}, err - } return Request{ Resolution: image.Rect(0, 0, width, height), Generator: generator, Encoder: encoder, Checksum: checksum, - Processor: processor, }, nil } diff --git a/services/thumbnails/pkg/thumbnail/thumbnail_test.go b/services/thumbnails/pkg/thumbnail/thumbnail_test.go index 2a046e15f51..660d2c8dad5 100644 --- a/services/thumbnails/pkg/thumbnail/thumbnail_test.go +++ b/services/thumbnails/pkg/thumbnail/thumbnail_test.go @@ -1,15 +1,17 @@ package thumbnail import ( - "github.com/owncloud/ocis/v2/services/thumbnails/pkg/errors" - "github.com/owncloud/ocis/v2/services/thumbnails/pkg/preprocessor" - "github.com/stretchr/testify/assert" "image" "os" "path" "path/filepath" + "reflect" "testing" + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/errors" + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/preprocessor" + "github.com/stretchr/testify/assert" + "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" @@ -131,11 +133,13 @@ func TestPrepareRequest(t *testing.T) { t.Errorf("PrepareRequest() error = %v, wantErr %v", err, tt.wantErr) return } - // funcs are not reflactable, ignore - if diff := cmp.Diff(tt.want, got, cmpopts.IgnoreFields(Request{}, "Processor")); diff != "" { + if diff := cmp.Diff(tt.want, got, cmpopts.IgnoreFields(Request{}, "Generator")); diff != "" { t.Errorf("PrepareRequest(): %v", diff) } + if reflect.TypeOf(got.Generator) != reflect.TypeOf(tt.want.Generator) { + t.Errorf("PrepareRequest() = %v, want %v", reflect.TypeOf(got.Generator), reflect.TypeOf(tt.want.Generator)) + } }) } } From 57a6b210b282ae05e3d8478f72f4a2ff7815b2a8 Mon Sep 17 00:00:00 2001 From: Ralf Haferkamp Date: Mon, 14 Oct 2024 15:49:06 +0200 Subject: [PATCH 02/11] refactor(thumbnails): separate out 'kovidgoyal/imaging' dependencies Move code using the 'kovidgoyal/imaging' package to separate files to make it easier to create alternative implementations based on build tags. --- .../pkg/preprocessor/preprocessor.go | 12 ----- .../pkg/preprocessor/preprocessor_imaging.go | 20 +++++++ services/thumbnails/pkg/thumbnail/encoding.go | 47 ---------------- .../pkg/thumbnail/encoding_imaging.go | 54 +++++++++++++++++++ .../thumbnails/pkg/thumbnail/generator.go | 39 ++++---------- .../pkg/thumbnail/generator_simple.go | 44 +++++++++++++++ .../thumbnails/pkg/thumbnail/thumbnail.go | 14 ++--- .../pkg/thumbnail/thumbnail_test.go | 4 ++ 8 files changed, 137 insertions(+), 97 deletions(-) create mode 100644 services/thumbnails/pkg/preprocessor/preprocessor_imaging.go create mode 100644 services/thumbnails/pkg/thumbnail/encoding_imaging.go create mode 100644 services/thumbnails/pkg/thumbnail/generator_simple.go diff --git a/services/thumbnails/pkg/preprocessor/preprocessor.go b/services/thumbnails/pkg/preprocessor/preprocessor.go index 4254c1a68b3..d4626b11e24 100644 --- a/services/thumbnails/pkg/preprocessor/preprocessor.go +++ b/services/thumbnails/pkg/preprocessor/preprocessor.go @@ -27,18 +27,6 @@ type FileConverter interface { Convert(r io.Reader) (interface{}, error) } -// ImageDecoder is a converter for the image file -type ImageDecoder struct{} - -// Convert reads the image file and returns the thumbnail image -func (i ImageDecoder) Convert(r io.Reader) (interface{}, error) { - img, err := imaging.Decode(r, imaging.AutoOrientation(true)) - if err != nil { - return nil, errors.Wrap(err, `could not decode the image`) - } - return img, nil -} - // GifDecoder is a converter for the gif file type GifDecoder struct{} diff --git a/services/thumbnails/pkg/preprocessor/preprocessor_imaging.go b/services/thumbnails/pkg/preprocessor/preprocessor_imaging.go new file mode 100644 index 00000000000..99c7d91a832 --- /dev/null +++ b/services/thumbnails/pkg/preprocessor/preprocessor_imaging.go @@ -0,0 +1,20 @@ +package preprocessor + +import ( + "io" + + "github.com/kovidgoyal/imaging" + "github.com/pkg/errors" +) + +// ImageDecoder is a converter for the image file +type ImageDecoder struct{} + +// Convert reads the image file and returns the thumbnail image +func (i ImageDecoder) Convert(r io.Reader) (interface{}, error) { + img, err := imaging.Decode(r, imaging.AutoOrientation(true)) + if err != nil { + return nil, errors.Wrap(err, `could not decode the image`) + } + return img, nil +} diff --git a/services/thumbnails/pkg/thumbnail/encoding.go b/services/thumbnails/pkg/thumbnail/encoding.go index 2cf22ba360b..917642db98a 100644 --- a/services/thumbnails/pkg/thumbnail/encoding.go +++ b/services/thumbnails/pkg/thumbnail/encoding.go @@ -1,10 +1,7 @@ package thumbnail import ( - "image" "image/gif" - "image/jpeg" - "image/png" "io" "strings" @@ -29,50 +26,6 @@ type Encoder interface { MimeType() string } -// PngEncoder encodes to png -type PngEncoder struct{} - -// Encode encodes to png format -func (e PngEncoder) Encode(w io.Writer, img interface{}) error { - m, ok := img.(image.Image) - if !ok { - return errors.ErrInvalidType - } - return png.Encode(w, m) -} - -// Types returns the png suffix -func (e PngEncoder) Types() []string { - return []string{typePng} -} - -// MimeType returns the mimetype for png files. -func (e PngEncoder) MimeType() string { - return "image/png" -} - -// JpegEncoder encodes to jpg -type JpegEncoder struct{} - -// Encode encodes to jpg -func (e JpegEncoder) Encode(w io.Writer, img interface{}) error { - m, ok := img.(image.Image) - if !ok { - return errors.ErrInvalidType - } - return jpeg.Encode(w, m, nil) -} - -// Types returns the jpg suffixes. -func (e JpegEncoder) Types() []string { - return []string{typeJpeg, typeJpg} -} - -// MimeType returns the mimetype for jpg files. -func (e JpegEncoder) MimeType() string { - return "image/jpeg" -} - // GifEncoder encodes to gif type GifEncoder struct{} diff --git a/services/thumbnails/pkg/thumbnail/encoding_imaging.go b/services/thumbnails/pkg/thumbnail/encoding_imaging.go new file mode 100644 index 00000000000..7ae2c09b6bb --- /dev/null +++ b/services/thumbnails/pkg/thumbnail/encoding_imaging.go @@ -0,0 +1,54 @@ +package thumbnail + +import ( + "image" + "image/jpeg" + "image/png" + "io" + + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/errors" +) + +// PngEncoder encodes to png +type PngEncoder struct{} + +// Encode encodes to png format +func (e PngEncoder) Encode(w io.Writer, img interface{}) error { + m, ok := img.(image.Image) + if !ok { + return errors.ErrInvalidType + } + return png.Encode(w, m) +} + +// Types returns the png suffix +func (e PngEncoder) Types() []string { + return []string{typePng} +} + +// MimeType returns the mimetype for png files. +func (e PngEncoder) MimeType() string { + return "image/png" +} + +// JpegEncoder encodes to jpg +type JpegEncoder struct{} + +// Encode encodes to jpg +func (e JpegEncoder) Encode(w io.Writer, img interface{}) error { + m, ok := img.(image.Image) + if !ok { + return errors.ErrInvalidType + } + return jpeg.Encode(w, m, nil) +} + +// Types returns the jpg suffixes. +func (e JpegEncoder) Types() []string { + return []string{typeJpeg, typeJpg} +} + +// MimeType returns the mimetype for jpg files. +func (e JpegEncoder) MimeType() string { + return "image/jpeg" +} diff --git a/services/thumbnails/pkg/thumbnail/generator.go b/services/thumbnails/pkg/thumbnail/generator.go index 463daddcead..9c90d33f389 100644 --- a/services/thumbnails/pkg/thumbnail/generator.go +++ b/services/thumbnails/pkg/thumbnail/generator.go @@ -14,44 +14,17 @@ import ( // Generator generates a web friendly file version. type Generator interface { Generate(size image.Rectangle, img interface{}) (interface{}, error) + Dimensions(img interface{}) (image.Rectangle, error) ProcessorID() string } -// SimpleGenerator is the default image generator and is used for all image types expect gif. -type SimpleGenerator struct { - processor Processor -} - -func NewSimpleGenerator(filetype, process string) (SimpleGenerator, error) { - processor, err := ProcessorFor(filetype, process) - if err != nil { - return SimpleGenerator{}, err - } - return SimpleGenerator{processor: processor}, nil -} - -// ProcessorID returns the processor identification. -func (g SimpleGenerator) ProcessorID() string { - return g.processor.ID() -} - -// Generate generates a alternative image version. -func (g SimpleGenerator) Generate(size image.Rectangle, img interface{}) (interface{}, error) { - m, ok := img.(image.Image) - if !ok { - return nil, errors.ErrInvalidType - } - - return g.processor.Process(m, size.Dx(), size.Dy(), imaging.Lanczos), nil -} - // GifGenerator is used to create a web friendly version of the provided gif image. type GifGenerator struct { processor Processor } func NewGifGenerator(filetype, process string) (GifGenerator, error) { - processor, err := ProcessorFor(filetype, process) + processor, err := ProcessorFor(process, filetype) if err != nil { return GifGenerator{}, err } @@ -97,6 +70,14 @@ func (g GifGenerator) Generate(size image.Rectangle, img interface{}) (interface return m, nil } +func (g GifGenerator) Dimensions(img interface{}) (image.Rectangle, error) { + m, ok := img.(*gif.GIF) + if !ok { + return image.Rectangle{}, errors.ErrInvalidType + } + return m.Image[0].Bounds(), nil +} + func (g GifGenerator) imageToPaletted(img image.Image, p color.Palette) *image.Paletted { b := img.Bounds() pm := image.NewPaletted(b, p) diff --git a/services/thumbnails/pkg/thumbnail/generator_simple.go b/services/thumbnails/pkg/thumbnail/generator_simple.go new file mode 100644 index 00000000000..d23af905d7b --- /dev/null +++ b/services/thumbnails/pkg/thumbnail/generator_simple.go @@ -0,0 +1,44 @@ +package thumbnail + +import ( + "image" + + "github.com/kovidgoyal/imaging" + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/errors" +) + +// SimpleGenerator is the default image generator and is used for all image types expect gif. +type SimpleGenerator struct { + processor Processor +} + +func NewSimpleGenerator(filetype, process string) (SimpleGenerator, error) { + processor, err := ProcessorFor(process, filetype) + if err != nil { + return SimpleGenerator{}, err + } + return SimpleGenerator{processor: processor}, nil +} + +// ProcessorID returns the processor identification. +func (g SimpleGenerator) ProcessorID() string { + return g.processor.ID() +} + +// Generate generates a alternative image version. +func (g SimpleGenerator) Generate(size image.Rectangle, img interface{}) (interface{}, error) { + m, ok := img.(image.Image) + if !ok { + return nil, errors.ErrInvalidType + } + + return g.processor.Process(m, size.Dx(), size.Dy(), imaging.Lanczos), nil +} + +func (g SimpleGenerator) Dimensions(img interface{}) (image.Rectangle, error) { + m, ok := img.(image.Image) + if !ok { + return image.Rectangle{}, errors.ErrInvalidType + } + return m.Bounds(), nil +} diff --git a/services/thumbnails/pkg/thumbnail/thumbnail.go b/services/thumbnails/pkg/thumbnail/thumbnail.go index adbb13d6c46..68559e5ea93 100644 --- a/services/thumbnails/pkg/thumbnail/thumbnail.go +++ b/services/thumbnails/pkg/thumbnail/thumbnail.go @@ -3,7 +3,6 @@ package thumbnail import ( "bytes" "image" - "image/gif" "mime" "github.com/owncloud/ocis/v2/ocis-pkg/log" @@ -70,15 +69,12 @@ type SimpleManager struct { // Generate creates a thumbnail and stores it func (s SimpleManager) Generate(r Request, img interface{}) (string, error) { var match image.Rectangle - var inputDimensions image.Rectangle - switch m := img.(type) { - case *gif.GIF: - match = s.resolutions.ClosestMatch(r.Resolution, m.Image[0].Bounds()) - inputDimensions = m.Image[0].Bounds() - case image.Image: - match = s.resolutions.ClosestMatch(r.Resolution, m.Bounds()) - inputDimensions = m.Bounds() + + inputDimensions, err := r.Generator.Dimensions(img) + if err != nil { + return "", err } + match = s.resolutions.ClosestMatch(r.Resolution, inputDimensions) // validate max input image dimensions - 6016x4000 if inputDimensions.Size().X > s.maxDimension.X || inputDimensions.Size().Y > s.maxDimension.Y { diff --git a/services/thumbnails/pkg/thumbnail/thumbnail_test.go b/services/thumbnails/pkg/thumbnail/thumbnail_test.go index 660d2c8dad5..9998b69aeab 100644 --- a/services/thumbnails/pkg/thumbnail/thumbnail_test.go +++ b/services/thumbnails/pkg/thumbnail/thumbnail_test.go @@ -183,6 +183,10 @@ func TestPreviewGenerationTooBigImage(t *testing.T) { ext := path.Ext(tt.fileName) req.Encoder, _ = EncoderForType(ext) + req.Generator, err = GeneratorFor(ext, "fit") + if err != nil { + return + } generate, err := sut.Generate(req, convert) if err != nil { return From 4e8a23f09da5e66402b7a7978cda9349164bcf3f Mon Sep 17 00:00:00 2001 From: Ralf Haferkamp Date: Mon, 14 Oct 2024 17:46:30 +0200 Subject: [PATCH 03/11] feat(thumbnails): optional libvips based thumbnail generation Can be enabled by setting the 'enable_vips' tag on 'go build' --- go.mod | 1 + go.sum | 6 + .../pkg/preprocessor/preprocessor_imaging.go | 2 + .../pkg/preprocessor/preprocessor_vips.go | 20 + .../pkg/thumbnail/encoding_imaging.go | 2 + .../thumbnails/pkg/thumbnail/encoding_vips.go | 66 + .../pkg/thumbnail/generator_simple.go | 2 + .../pkg/thumbnail/generator_vips.go | 62 + .../github.com/davidbyttow/govips/v2/LICENSE | 24 + .../davidbyttow/govips/v2/vips/arithmetic.c | 71 + .../davidbyttow/govips/v2/vips/arithmetic.go | 176 ++ .../davidbyttow/govips/v2/vips/arithmetic.h | 20 + .../davidbyttow/govips/v2/vips/color.c | 22 + .../davidbyttow/govips/v2/vips/color.go | 96 + .../davidbyttow/govips/v2/vips/color.h | 10 + .../davidbyttow/govips/v2/vips/composite.go | 27 + .../davidbyttow/govips/v2/vips/conversion.c | 380 +++ .../davidbyttow/govips/v2/vips/conversion.go | 520 ++++ .../davidbyttow/govips/v2/vips/conversion.h | 70 + .../davidbyttow/govips/v2/vips/convolution.c | 14 + .../davidbyttow/govips/v2/vips/convolution.go | 40 + .../davidbyttow/govips/v2/vips/convolution.h | 9 + .../davidbyttow/govips/v2/vips/create.c | 24 + .../davidbyttow/govips/v2/vips/create.go | 37 + .../davidbyttow/govips/v2/vips/create.h | 12 + .../davidbyttow/govips/v2/vips/draw.c | 24 + .../davidbyttow/govips/v2/vips/draw.go | 21 + .../davidbyttow/govips/v2/vips/draw.h | 7 + .../davidbyttow/govips/v2/vips/error.go | 39 + .../davidbyttow/govips/v2/vips/foreign.c | 578 +++++ .../davidbyttow/govips/v2/vips/foreign.go | 502 ++++ .../davidbyttow/govips/v2/vips/foreign.h | 141 ++ .../davidbyttow/govips/v2/vips/govips.c | 27 + .../davidbyttow/govips/v2/vips/govips.go | 265 ++ .../davidbyttow/govips/v2/vips/govips.h | 26 + .../davidbyttow/govips/v2/vips/header.c | 122 + .../davidbyttow/govips/v2/vips/header.go | 289 +++ .../davidbyttow/govips/v2/vips/header.h | 41 + .../govips/v2/vips/icc_profiles.go | 675 +++++ .../davidbyttow/govips/v2/vips/image.c | 9 + .../davidbyttow/govips/v2/vips/image.go | 2162 +++++++++++++++++ .../davidbyttow/govips/v2/vips/image.h | 8 + .../davidbyttow/govips/v2/vips/label.c | 37 + .../davidbyttow/govips/v2/vips/label.go | 84 + .../davidbyttow/govips/v2/vips/label.h | 21 + .../davidbyttow/govips/v2/vips/lang.go | 49 + .../davidbyttow/govips/v2/vips/lang.h | 4 + .../davidbyttow/govips/v2/vips/logging.go | 98 + .../davidbyttow/govips/v2/vips/math.go | 57 + .../davidbyttow/govips/v2/vips/morphology.c | 6 + .../davidbyttow/govips/v2/vips/morphology.go | 17 + .../davidbyttow/govips/v2/vips/morphology.h | 6 + .../davidbyttow/govips/v2/vips/resample.c | 61 + .../davidbyttow/govips/v2/vips/resample.go | 146 ++ .../davidbyttow/govips/v2/vips/resample.h | 24 + .../davidbyttow/govips/v2/vips/stats.go | 56 + .../govips/v2/vips/test_resources.go | 6 + vendor/modules.txt | 3 + 58 files changed, 7324 insertions(+) create mode 100644 services/thumbnails/pkg/preprocessor/preprocessor_vips.go create mode 100644 services/thumbnails/pkg/thumbnail/encoding_vips.go create mode 100644 services/thumbnails/pkg/thumbnail/generator_vips.go create mode 100644 vendor/github.com/davidbyttow/govips/v2/LICENSE create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/arithmetic.c create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/arithmetic.go create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/arithmetic.h create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/color.c create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/color.go create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/color.h create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/composite.go create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/conversion.c create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/conversion.go create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/conversion.h create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/convolution.c create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/convolution.go create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/convolution.h create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/create.c create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/create.go create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/create.h create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/draw.c create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/draw.go create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/draw.h create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/error.go create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/foreign.c create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/foreign.go create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/foreign.h create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/govips.c create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/govips.go create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/govips.h create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/header.c create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/header.go create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/header.h create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/icc_profiles.go create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/image.c create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/image.go create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/image.h create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/label.c create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/label.go create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/label.h create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/lang.go create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/lang.h create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/logging.go create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/math.go create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/morphology.c create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/morphology.go create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/morphology.h create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/resample.c create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/resample.go create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/resample.h create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/stats.go create mode 100644 vendor/github.com/davidbyttow/govips/v2/vips/test_resources.go diff --git a/go.mod b/go.mod index e3c7f1b9219..c8db95b41ca 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( github.com/coreos/go-oidc/v3 v3.11.0 github.com/cs3org/go-cs3apis v0.0.0-20240724121416-062c4e3046cb github.com/cs3org/reva/v2 v2.25.1-0.20241016145214-e5baaccf6614 + github.com/davidbyttow/govips/v2 v2.15.0 github.com/dhowden/tag v0.0.0-20230630033851-978a0926ee25 github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e github.com/egirna/icap-client v0.1.1 diff --git a/go.sum b/go.sum index 449c064ac0a..030e65c2a81 100644 --- a/go.sum +++ b/go.sum @@ -259,6 +259,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.15.0 h1:h3lF+rQElBzGXbQSSPqmE3XGySPhcQo2x3t5l/dZ+pU= +github.com/davidbyttow/govips/v2 v2.15.0/go.mod h1:3OQCHj0nf5Mnrplh5VlNvmx3IhJXyxbAoTJZPflUjmM= 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/deepmap/oapi-codegen v1.3.11/go.mod h1:suMvK7+rKlx3+tpa8ByptmvoXbAV70wERKTOGH3hLp0= @@ -845,6 +847,7 @@ github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDm github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nrdcg/auroradns v1.0.1/go.mod h1:y4pc0i9QXYlFCWrhWrUSIETnZgrf4KuwjDIWmmXo3JI= github.com/nrdcg/desec v0.5.0/go.mod h1:2ejvMazkav1VdDbv2HeQO7w+Ta1CGHqzQr27ZBYTuEQ= github.com/nrdcg/dnspod-go v0.4.0/go.mod h1:vZSoFSFeQVm2gWLMkyX61LZ8HI3BaqtHZWgPTGKr6KQ= @@ -1248,6 +1251,7 @@ golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.10.0/go.mod h1:jtrku+n79PfroUbvDdeUWMAI+heR786BofxrbiSF+J0= golang.org/x/image v0.20.0 h1:7cVCUjQwfL18gyBJOmYvptfSHS8Fb3YUDtfLIZ7Nbpw= golang.org/x/image v0.20.0/go.mod h1:0a88To4CYVBAHp5FXJm8o7QbUl37Vd85ply1vyD8auM= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -1468,6 +1472,7 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= @@ -1640,6 +1645,7 @@ gopkg.in/cenkalti/backoff.v1 v1.1.0/go.mod h1:J6Vskwqd+OMVJl8C33mmtxTBs2gyzfv7UD gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= diff --git a/services/thumbnails/pkg/preprocessor/preprocessor_imaging.go b/services/thumbnails/pkg/preprocessor/preprocessor_imaging.go index 99c7d91a832..4510e234e62 100644 --- a/services/thumbnails/pkg/preprocessor/preprocessor_imaging.go +++ b/services/thumbnails/pkg/preprocessor/preprocessor_imaging.go @@ -1,3 +1,5 @@ +//go:build !enable_vips + package preprocessor import ( diff --git a/services/thumbnails/pkg/preprocessor/preprocessor_vips.go b/services/thumbnails/pkg/preprocessor/preprocessor_vips.go new file mode 100644 index 00000000000..087bafb1b71 --- /dev/null +++ b/services/thumbnails/pkg/preprocessor/preprocessor_vips.go @@ -0,0 +1,20 @@ +//go:build enable_vips + +package preprocessor + +import ( + "io" + + "github.com/davidbyttow/govips/v2/vips" +) + +func init() { + vips.LoggingSettings(nil, vips.LogLevelError) +} + +type ImageDecoder struct{} + +func (v ImageDecoder) Convert(r io.Reader) (interface{}, error) { + img, err := vips.NewImageFromReader(r) + return img, err +} diff --git a/services/thumbnails/pkg/thumbnail/encoding_imaging.go b/services/thumbnails/pkg/thumbnail/encoding_imaging.go index 7ae2c09b6bb..c46352d96c1 100644 --- a/services/thumbnails/pkg/thumbnail/encoding_imaging.go +++ b/services/thumbnails/pkg/thumbnail/encoding_imaging.go @@ -1,3 +1,5 @@ +//go:build !enable_vips + package thumbnail import ( diff --git a/services/thumbnails/pkg/thumbnail/encoding_vips.go b/services/thumbnails/pkg/thumbnail/encoding_vips.go new file mode 100644 index 00000000000..d6371296ada --- /dev/null +++ b/services/thumbnails/pkg/thumbnail/encoding_vips.go @@ -0,0 +1,66 @@ +//go:build enable_vips + +package thumbnail + +import ( + "io" + + "github.com/davidbyttow/govips/v2/vips" + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/errors" +) + +// PngEncoder encodes to png +type PngEncoder struct{} + +// Encode encodes to png format +func (e PngEncoder) Encode(w io.Writer, img interface{}) error { + m, ok := img.(*vips.ImageRef) + if !ok { + return errors.ErrInvalidType + } + + buf, _, err := m.ExportPng(vips.NewPngExportParams()) + if err != nil { + return err + } + _, err = w.Write(buf) + return err +} + +// Types returns the png suffix +func (e PngEncoder) Types() []string { + return []string{typePng} +} + +// MimeType returns the mimetype for png files. +func (e PngEncoder) MimeType() string { + return "image/png" +} + +// JpegEncoder encodes to jpg +type JpegEncoder struct{} + +// Encode encodes to jpg +func (e JpegEncoder) Encode(w io.Writer, img interface{}) error { + m, ok := img.(*vips.ImageRef) + if !ok { + return errors.ErrInvalidType + } + + buf, _, err := m.ExportJpeg(vips.NewJpegExportParams()) + if err != nil { + return err + } + _, err = w.Write(buf) + return err +} + +// Types returns the jpg suffixes. +func (e JpegEncoder) Types() []string { + return []string{typeJpeg, typeJpg} +} + +// MimeType returns the mimetype for jpg files. +func (e JpegEncoder) MimeType() string { + return "image/jpeg" +} diff --git a/services/thumbnails/pkg/thumbnail/generator_simple.go b/services/thumbnails/pkg/thumbnail/generator_simple.go index d23af905d7b..557ee6d842b 100644 --- a/services/thumbnails/pkg/thumbnail/generator_simple.go +++ b/services/thumbnails/pkg/thumbnail/generator_simple.go @@ -1,3 +1,5 @@ +//go:build !enable_vips + package thumbnail import ( diff --git a/services/thumbnails/pkg/thumbnail/generator_vips.go b/services/thumbnails/pkg/thumbnail/generator_vips.go new file mode 100644 index 00000000000..c2c1bd37154 --- /dev/null +++ b/services/thumbnails/pkg/thumbnail/generator_vips.go @@ -0,0 +1,62 @@ +//go:build enable_vips + +package thumbnail + +import ( + "image" + "strings" + + "github.com/davidbyttow/govips/v2/vips" + "github.com/owncloud/ocis/v2/services/thumbnails/pkg/errors" +) + +// SimpleGenerator is the default image generator and is used for all image types expect gif. +type SimpleGenerator struct { + crop vips.Interesting + size vips.Size + process string +} + +func NewSimpleGenerator(filetype, process string) (SimpleGenerator, error) { + switch strings.ToLower(process) { + case "thumbnail": + return SimpleGenerator{crop: vips.InterestingAttention, process: process, size: vips.SizeBoth}, nil + case "fit": + return SimpleGenerator{crop: vips.InterestingNone, process: process, size: vips.SizeBoth}, nil + case "resize": + return SimpleGenerator{crop: vips.InterestingNone, process: process, size: vips.SizeForce}, nil + default: + return SimpleGenerator{crop: vips.InterestingNone, process: process}, nil + } +} + +// ProcessorID returns the processor identification. +func (g SimpleGenerator) ProcessorID() string { + return g.process +} + +// Generate generates a alternative image version. +func (g SimpleGenerator) Generate(size image.Rectangle, img interface{}) (interface{}, error) { + m, ok := img.(*vips.ImageRef) + if !ok { + return nil, errors.ErrInvalidType + } + + if err := m.ThumbnailWithSize(size.Dx(), 0, g.crop, g.size); err != nil { + return nil, err + } + + if err := m.RemoveMetadata(); err != nil { + return nil, err + } + + return m, nil +} + +func (g SimpleGenerator) Dimensions(img interface{}) (image.Rectangle, error) { + m, ok := img.(*vips.ImageRef) + if !ok { + return image.Rectangle{}, errors.ErrInvalidType + } + return image.Rect(0, 0, m.Width(), m.Height()), nil +} diff --git a/vendor/github.com/davidbyttow/govips/v2/LICENSE b/vendor/github.com/davidbyttow/govips/v2/LICENSE new file mode 100644 index 00000000000..8d0891d788e --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/LICENSE @@ -0,0 +1,24 @@ +The MIT License + +Copyright (c) Simple Things LLC and contributors + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/arithmetic.c b/vendor/github.com/davidbyttow/govips/v2/vips/arithmetic.c new file mode 100644 index 00000000000..557f613cbd9 --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/arithmetic.c @@ -0,0 +1,71 @@ +#include "arithmetic.h" + +int add(VipsImage *left, VipsImage *right, VipsImage **out) { + return vips_add(left, right, out, NULL); +} + +int multiply(VipsImage *left, VipsImage *right, VipsImage **out) { + return vips_multiply(left, right, out, NULL); +} + +int divide(VipsImage *left, VipsImage *right, VipsImage **out) { + return vips_divide(left, right, out, NULL); +} + +int linear(VipsImage *in, VipsImage **out, double *a, double *b, int n) { + return vips_linear(in, out, a, b, n, NULL); +} + +int linear1(VipsImage *in, VipsImage **out, double a, double b) { + return vips_linear1(in, out, a, b, NULL); +} + +int invert_image(VipsImage *in, VipsImage **out) { + return vips_invert(in, out, NULL); +} + +int average(VipsImage *in, double *out) { + return vips_avg(in, out, NULL); +} + +int find_trim(VipsImage *in, int *left, int *top, int *width, int *height, + double threshold, double r, double g, double b) { + + if (in->Type == VIPS_INTERPRETATION_RGB16 || in->Type == VIPS_INTERPRETATION_GREY16) { + r = 65535 * r / 255; + g = 65535 * g / 255; + b = 65535 * b / 255; + } + + double background[3] = {r, g, b}; + VipsArrayDouble *vipsBackground = vips_array_double_new(background, 3); + + int code = vips_find_trim(in, left, top, width, height, "threshold", threshold, "background", vipsBackground, NULL); + + vips_area_unref(VIPS_AREA(vipsBackground)); + return code; +} + +int getpoint(VipsImage *in, double **vector, int n, int x, int y) { + return vips_getpoint(in, vector, &n, x, y, NULL); +} + +int stats(VipsImage *in, VipsImage **out) { + return vips_stats(in, out, NULL); +} + +int hist_find(VipsImage *in, VipsImage **out) { + return vips_hist_find(in, out, NULL); +} + +int hist_cum(VipsImage *in, VipsImage **out) { + return vips_hist_cum(in, out, NULL); +} + +int hist_norm(VipsImage *in, VipsImage **out) { + return vips_hist_norm(in, out, NULL); +} + +int hist_entropy(VipsImage *in, double *out) { + return vips_hist_entropy(in, out, NULL); +} diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/arithmetic.go b/vendor/github.com/davidbyttow/govips/v2/vips/arithmetic.go new file mode 100644 index 00000000000..3bdf5baa1d5 --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/arithmetic.go @@ -0,0 +1,176 @@ +package vips + +// #include "arithmetic.h" +import "C" +import "unsafe" + +// https://libvips.github.io/libvips/API/current/libvips-arithmetic.html#vips-add +func vipsAdd(left *C.VipsImage, right *C.VipsImage) (*C.VipsImage, error) { + incOpCounter("add") + var out *C.VipsImage + + if err := C.add(left, right, &out); err != 0 { + return nil, handleImageError(out) + } + + return out, nil +} + +// https://libvips.github.io/libvips/API/current/libvips-arithmetic.html#vips-multiply +func vipsMultiply(left *C.VipsImage, right *C.VipsImage) (*C.VipsImage, error) { + incOpCounter("multiply") + var out *C.VipsImage + + if err := C.multiply(left, right, &out); err != 0 { + return nil, handleImageError(out) + } + + return out, nil +} + +// https://libvips.github.io/libvips/API/current/libvips-arithmetic.html#vips-divide +func vipsDivide(left *C.VipsImage, right *C.VipsImage) (*C.VipsImage, error) { + incOpCounter("divide") + var out *C.VipsImage + + if err := C.divide(left, right, &out); err != 0 { + return nil, handleImageError(out) + } + + return out, nil +} + +// https://libvips.github.io/libvips/API/current/libvips-arithmetic.html#vips-linear +func vipsLinear(in *C.VipsImage, a, b []float64, n int) (*C.VipsImage, error) { + incOpCounter("linear") + var out *C.VipsImage + + if err := C.linear(in, &out, (*C.double)(&a[0]), (*C.double)(&b[0]), C.int(n)); err != 0 { + return nil, handleImageError(out) + } + + return out, nil +} + +// https://libvips.github.io/libvips/API/current/libvips-arithmetic.html#vips-linear1 +func vipsLinear1(in *C.VipsImage, a, b float64) (*C.VipsImage, error) { + incOpCounter("linear1") + var out *C.VipsImage + + if err := C.linear1(in, &out, C.double(a), C.double(b)); err != 0 { + return nil, handleImageError(out) + } + + return out, nil +} + +// https://libvips.github.io/libvips/API/current/libvips-arithmetic.html#vips-invert +func vipsInvert(in *C.VipsImage) (*C.VipsImage, error) { + incOpCounter("invert") + var out *C.VipsImage + + if err := C.invert_image(in, &out); err != 0 { + return nil, handleImageError(out) + } + + return out, nil +} + +// https://libvips.github.io/libvips/API/current/libvips-arithmetic.html#vips-avg +func vipsAverage(in *C.VipsImage) (float64, error) { + incOpCounter("average") + var out C.double + + if err := C.average(in, &out); err != 0 { + return 0, handleVipsError() + } + + return float64(out), nil +} + +// https://libvips.github.io/libvips/API/current/libvips-arithmetic.html#vips-find-trim +func vipsFindTrim(in *C.VipsImage, threshold float64, backgroundColor *Color) (int, int, int, int, error) { + incOpCounter("findTrim") + var left, top, width, height C.int + + if err := C.find_trim(in, &left, &top, &width, &height, C.double(threshold), C.double(backgroundColor.R), + C.double(backgroundColor.G), C.double(backgroundColor.B)); err != 0 { + return -1, -1, -1, -1, handleVipsError() + } + + return int(left), int(top), int(width), int(height), nil +} + +// https://libvips.github.io/libvips/API/current/libvips-arithmetic.html#vips-getpoint +func vipsGetPoint(in *C.VipsImage, n int, x int, y int) ([]float64, error) { + incOpCounter("getpoint") + var out *C.double + 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 +} + +// https://www.libvips.org/API/current/libvips-arithmetic.html#vips-stats +func vipsStats(in *C.VipsImage) (*C.VipsImage, error) { + incOpCounter("stats") + var out *C.VipsImage + + if err := C.stats(in, &out); err != 0 { + return nil, handleImageError(out) + } + + return out, nil +} + +// https://www.libvips.org/API/current/libvips-arithmetic.html#vips-hist-find +func vipsHistFind(in *C.VipsImage) (*C.VipsImage, error) { + incOpCounter("histFind") + var out *C.VipsImage + + if err := C.hist_find(in, &out); err != 0 { + return nil, handleImageError(out) + } + + return out, nil +} + +// https://www.libvips.org/API/current/libvips-histogram.html#vips-hist-norm +func vipsHistNorm(in *C.VipsImage) (*C.VipsImage, error) { + incOpCounter("histNorm") + var out *C.VipsImage + + if err := C.hist_norm(in, &out); err != 0 { + return nil, handleImageError(out) + } + + return out, nil +} + +// https://www.libvips.org/API/current/libvips-histogram.html#vips-hist-cum +func vipsHistCum(in *C.VipsImage) (*C.VipsImage, error) { + incOpCounter("histCum") + var out *C.VipsImage + + if err := C.hist_cum(in, &out); err != 0 { + return nil, handleImageError(out) + } + + return out, nil +} + +// https://www.libvips.org/API/current/libvips-histogram.html#vips-hist-entropy +func vipsHistEntropy(in *C.VipsImage) (float64, error) { + incOpCounter("histEntropy") + var out C.double + + if err := C.hist_entropy(in, &out); err != 0 { + return 0, handleVipsError() + } + + return float64(out), nil +} diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/arithmetic.h b/vendor/github.com/davidbyttow/govips/v2/vips/arithmetic.h new file mode 100644 index 00000000000..693b4fdde88 --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/arithmetic.h @@ -0,0 +1,20 @@ +// https://libvips.github.io/libvips/API/current/libvips-arithmetic.html + +#include +#include + +int add(VipsImage *left, VipsImage *right, VipsImage **out); +int multiply(VipsImage *left, VipsImage *right, VipsImage **out); +int divide(VipsImage *left, VipsImage *right, VipsImage **out); +int linear(VipsImage *in, VipsImage **out, double *a, double *b, int n); +int linear1(VipsImage *in, VipsImage **out, double a, double b); +int invert_image(VipsImage *in, VipsImage **out); +int average(VipsImage *in, double *out); +int find_trim(VipsImage *in, int *left, int *top, int *width, int *height, + double threshold, double r, double g, double b); +int getpoint(VipsImage *in, double **vector, int n, int x, int y); +int stats(VipsImage *in, VipsImage **out); +int hist_find(VipsImage *in, VipsImage **out); +int hist_cum(VipsImage *in, VipsImage **out); +int hist_norm(VipsImage *in, VipsImage **out); +int hist_entropy(VipsImage *in, double *out); diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/color.c b/vendor/github.com/davidbyttow/govips/v2/vips/color.c new file mode 100644 index 00000000000..202440ee1b2 --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/color.c @@ -0,0 +1,22 @@ +#include "color.h" +#include + +int is_colorspace_supported(VipsImage *in) { + return vips_colourspace_issupported(in) ? 1 : 0; +} + +int to_colorspace(VipsImage *in, VipsImage **out, VipsInterpretation space) { + return vips_colourspace(in, out, space, NULL); +} + +// https://libvips.github.io/libvips/API/8.6/libvips-colour.html#vips-icc-transform +int icc_transform(VipsImage *in, VipsImage **out, const char *output_profile, const char *input_profile, VipsIntent intent, + int depth, gboolean embedded) { + return vips_icc_transform( + in, out, output_profile, + "input_profile", input_profile ? input_profile : "none", + "intent", intent, + "depth", depth ? depth : 8, + "embedded", embedded, + NULL); +} diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/color.go b/vendor/github.com/davidbyttow/govips/v2/vips/color.go new file mode 100644 index 00000000000..df5aed529a9 --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/color.go @@ -0,0 +1,96 @@ +package vips + +// #include "color.h" +import "C" + +// Color represents an RGB +type Color struct { + R, G, B uint8 +} + +// ColorRGBA represents an RGB with alpha channel (A) +type ColorRGBA struct { + R, G, B, A uint8 +} + +// Interpretation represents VIPS_INTERPRETATION type +type Interpretation int + +// Interpretation enum +const ( + InterpretationError Interpretation = C.VIPS_INTERPRETATION_ERROR + InterpretationMultiband Interpretation = C.VIPS_INTERPRETATION_MULTIBAND + InterpretationBW Interpretation = C.VIPS_INTERPRETATION_B_W + InterpretationHistogram Interpretation = C.VIPS_INTERPRETATION_HISTOGRAM + InterpretationXYZ Interpretation = C.VIPS_INTERPRETATION_XYZ + InterpretationLAB Interpretation = C.VIPS_INTERPRETATION_LAB + InterpretationCMYK Interpretation = C.VIPS_INTERPRETATION_CMYK + InterpretationLABQ Interpretation = C.VIPS_INTERPRETATION_LABQ + InterpretationRGB Interpretation = C.VIPS_INTERPRETATION_RGB + InterpretationRGB16 Interpretation = C.VIPS_INTERPRETATION_RGB16 + InterpretationCMC Interpretation = C.VIPS_INTERPRETATION_CMC + InterpretationLCH Interpretation = C.VIPS_INTERPRETATION_LCH + InterpretationLABS Interpretation = C.VIPS_INTERPRETATION_LABS + InterpretationSRGB Interpretation = C.VIPS_INTERPRETATION_sRGB + InterpretationYXY Interpretation = C.VIPS_INTERPRETATION_YXY + InterpretationFourier Interpretation = C.VIPS_INTERPRETATION_FOURIER + InterpretationGrey16 Interpretation = C.VIPS_INTERPRETATION_GREY16 + InterpretationMatrix Interpretation = C.VIPS_INTERPRETATION_MATRIX + InterpretationScRGB Interpretation = C.VIPS_INTERPRETATION_scRGB + InterpretationHSV Interpretation = C.VIPS_INTERPRETATION_HSV +) + +// Intent represents VIPS_INTENT type +type Intent int + +// Intent enum +const ( + IntentPerceptual Intent = C.VIPS_INTENT_PERCEPTUAL + IntentRelative Intent = C.VIPS_INTENT_RELATIVE + IntentSaturation Intent = C.VIPS_INTENT_SATURATION + IntentAbsolute Intent = C.VIPS_INTENT_ABSOLUTE + IntentLast Intent = C.VIPS_INTENT_LAST +) + +func vipsIsColorSpaceSupported(in *C.VipsImage) bool { + return C.is_colorspace_supported(in) == 1 +} + +// https://libvips.github.io/libvips/API/current/libvips-colour.html#vips-colourspace +func vipsToColorSpace(in *C.VipsImage, interpretation Interpretation) (*C.VipsImage, error) { + incOpCounter("to_colorspace") + var out *C.VipsImage + + inter := C.VipsInterpretation(interpretation) + + if err := C.to_colorspace(in, &out, inter); err != 0 { + return nil, handleImageError(out) + } + + return out, nil +} + +func vipsICCTransform(in *C.VipsImage, outputProfile string, inputProfile string, intent Intent, depth int, + embedded bool) (*C.VipsImage, error) { + var out *C.VipsImage + var cInputProfile *C.char + var cEmbedded C.gboolean + + cOutputProfile := C.CString(outputProfile) + defer freeCString(cOutputProfile) + + if inputProfile != "" { + cInputProfile = C.CString(inputProfile) + defer freeCString(cInputProfile) + } + + if embedded { + cEmbedded = C.TRUE + } + + if res := C.icc_transform(in, &out, cOutputProfile, cInputProfile, C.VipsIntent(intent), C.int(depth), cEmbedded); res != 0 { + return nil, handleImageError(out) + } + + return out, nil +} diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/color.h b/vendor/github.com/davidbyttow/govips/v2/vips/color.h new file mode 100644 index 00000000000..0f68796a6b5 --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/color.h @@ -0,0 +1,10 @@ +// https://libvips.github.io/libvips/API/current/libvips-colour.html + +#include +#include + +int is_colorspace_supported(VipsImage *in); +int to_colorspace(VipsImage *in, VipsImage **out, VipsInterpretation space); + +int icc_transform(VipsImage *in, VipsImage **out, const char *output_profile, const char *input_profile, VipsIntent intent, + int depth, gboolean embedded); diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/composite.go b/vendor/github.com/davidbyttow/govips/v2/vips/composite.go new file mode 100644 index 00000000000..209870928cf --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/composite.go @@ -0,0 +1,27 @@ +package vips + +// #include +import "C" + +// ImageComposite image to composite param +type ImageComposite struct { + Image *ImageRef + BlendMode BlendMode + X, Y int +} + +func toVipsCompositeStructs(r *ImageRef, datas []*ImageComposite) ([]*C.VipsImage, []C.int, []C.int, []C.int) { + ins := []*C.VipsImage{r.image} + modes := []C.int{} + xs := []C.int{} + ys := []C.int{} + + for _, image := range datas { + ins = append(ins, image.Image.image) + modes = append(modes, C.int(image.BlendMode)) + xs = append(xs, C.int(image.X)) + ys = append(ys, C.int(image.Y)) + } + + return ins, modes, xs, ys +} diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/conversion.c b/vendor/github.com/davidbyttow/govips/v2/vips/conversion.c new file mode 100644 index 00000000000..878e80818c1 --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/conversion.c @@ -0,0 +1,380 @@ +#include "conversion.h" + +int copy_image_changing_interpretation(VipsImage *in, VipsImage **out, + VipsInterpretation interpretation) { + return vips_copy(in, out, "interpretation", interpretation, NULL); +} + +int copy_image_changing_resolution(VipsImage *in, VipsImage **out, double xres, + double yres) { + return vips_copy(in, out, "xres", xres, "yres", yres, NULL); +} + +int copy_image(VipsImage *in, VipsImage **out) { + return vips_copy(in, out, NULL); +} + +int embed_image(VipsImage *in, VipsImage **out, int left, int top, int width, + int height, int extend) { + return vips_embed(in, out, left, top, width, height, "extend", extend, NULL); +} + +int embed_image_background(VipsImage *in, VipsImage **out, int left, int top, int width, + int height, double r, double g, double b, double a) { + + double background[3] = {r, g, b}; + double backgroundRGBA[4] = {r, g, b, a}; + + VipsArrayDouble *vipsBackground; + + if (in->Bands <= 3) { + vipsBackground = vips_array_double_new(background, 3); + } else { + vipsBackground = vips_array_double_new(backgroundRGBA, 4); + } + + int code = vips_embed(in, out, left, top, width, height, + "extend", VIPS_EXTEND_BACKGROUND, "background", vipsBackground, NULL); + + vips_area_unref(VIPS_AREA(vipsBackground)); + return code; +} + +int embed_multi_page_image(VipsImage *in, VipsImage **out, int left, int top, int width, + int height, int extend) { + VipsObject *base = VIPS_OBJECT(vips_image_new()); + int page_height = vips_image_get_page_height(in); + int in_width = in->Xsize; + int n_pages = in->Ysize / page_height; + + VipsImage **page = (VipsImage **) vips_object_local_array(base, n_pages); + VipsImage **copy = (VipsImage **) vips_object_local_array(base, 1); + + // split image into cropped frames + for (int i = 0; i < n_pages; i++) { + if ( + vips_extract_area(in, &page[i], 0, page_height * i, in_width, page_height, NULL) || + vips_embed(page[i], &page[i], left, top, width, height, "extend", extend, NULL) + ) { + g_object_unref(base); + return -1; + } + } + // reassemble frames and set page height + // copy before modifying metadata + if( + vips_arrayjoin(page, ©[0], n_pages, "across", 1, NULL) || + vips_copy(copy[0], out, NULL) + ) { + g_object_unref(base); + return -1; + } + vips_image_set_int(*out, VIPS_META_PAGE_HEIGHT, height); + g_object_unref(base); + return 0; +} + +int embed_multi_page_image_background(VipsImage *in, VipsImage **out, int left, int top, int width, + int height, double r, double g, double b, double a) { + double background[3] = {r, g, b}; + double backgroundRGBA[4] = {r, g, b, a}; + + VipsArrayDouble *vipsBackground; + + if (in->Bands <= 3) { + vipsBackground = vips_array_double_new(background, 3); + } else { + vipsBackground = vips_array_double_new(backgroundRGBA, 4); + } + VipsObject *base = VIPS_OBJECT(vips_image_new()); + int page_height = vips_image_get_page_height(in); + int in_width = in->Xsize; + int n_pages = in->Ysize / page_height; + + VipsImage **page = (VipsImage **) vips_object_local_array(base, n_pages); + VipsImage **copy = (VipsImage **) vips_object_local_array(base, 1); + + // split image into cropped frames + for (int i = 0; i < n_pages; i++) { + if ( + vips_extract_area(in, &page[i], 0, page_height * i, in_width, page_height, NULL) || + vips_embed(page[i], &page[i], left, top, width, height, + "extend", VIPS_EXTEND_BACKGROUND, "background", vipsBackground, NULL) + ) { + vips_area_unref(VIPS_AREA(vipsBackground)); + g_object_unref(base); + return -1; + } + } + // reassemble frames and set page height + // copy before modifying metadata + if( + vips_arrayjoin(page, ©[0], n_pages, "across", 1, NULL) || + vips_copy(copy[0], out, NULL) + ) { + vips_area_unref(VIPS_AREA(vipsBackground)); + g_object_unref(base); + return -1; + } + vips_image_set_int(*out, VIPS_META_PAGE_HEIGHT, height); + vips_area_unref(VIPS_AREA(vipsBackground)); + g_object_unref(base); + return 0; +} + +int flip_image(VipsImage *in, VipsImage **out, int direction) { + return vips_flip(in, out, direction, NULL); +} + +int recomb_image(VipsImage *in, VipsImage **out, VipsImage *m) { + return vips_recomb(in, out, m, NULL); +} + +int extract_image_area(VipsImage *in, VipsImage **out, int left, int top, + int width, int height) { + return vips_extract_area(in, out, left, top, width, height, NULL); +} + +int extract_area_multi_page(VipsImage *in, VipsImage **out, int left, int top, int width, int height) { + VipsObject *base = VIPS_OBJECT(vips_image_new()); + int page_height = vips_image_get_page_height(in); + int n_pages = in->Ysize / page_height; + + VipsImage **page = (VipsImage **) vips_object_local_array(base, n_pages); + VipsImage **copy = (VipsImage **) vips_object_local_array(base, 1); + + // split image into cropped frames + for (int i = 0; i < n_pages; i++) { + if(vips_extract_area(in, &page[i], left, page_height * i + top, width, height, NULL)) { + g_object_unref(base); + return -1; + } + } + // reassemble frames and set page height + // copy before modifying metadata + if( + vips_arrayjoin(page, ©[0], n_pages, "across", 1, NULL) || + vips_copy(copy[0], out, NULL) + ) { + g_object_unref(base); + return -1; + } + vips_image_set_int(*out, VIPS_META_PAGE_HEIGHT, height); + g_object_unref(base); + return 0; +} + +int extract_band(VipsImage *in, VipsImage **out, int band, int num) { + if (num > 0) { + return vips_extract_band(in, out, band, "n", num, NULL); + } + return vips_extract_band(in, out, band, NULL); +} + +int rot_image(VipsImage *in, VipsImage **out, VipsAngle angle) { + return vips_rot(in, out, angle, NULL); +} + +int autorot_image(VipsImage *in, VipsImage **out) { + return vips_autorot(in, out, NULL); +} + +int zoom_image(VipsImage *in, VipsImage **out, int xfac, int yfac) { + return vips_zoom(in, out, xfac, yfac, NULL); +} + +int bandjoin(VipsImage **in, VipsImage **out, int n) { + return vips_bandjoin(in, out, n, NULL); +} + +int bandjoin_const(VipsImage *in, VipsImage **out, double constants[], int n) { + return vips_bandjoin_const(in, out, constants, n, NULL); +} + +int similarity(VipsImage *in, VipsImage **out, double scale, double angle, + double r, double g, double b, double a, double idx, double idy, + double odx, double ody) { + if (is_16bit(in->Type)) { + r = 65535 * r / 255; + g = 65535 * g / 255; + b = 65535 * b / 255; + a = 65535 * a / 255; + } + + double background[3] = {r, g, b}; + double backgroundRGBA[4] = {r, g, b, a}; + + VipsArrayDouble *vipsBackground; + + // Ignore the alpha channel if the image doesn't have one + if (in->Bands <= 3) { + vipsBackground = vips_array_double_new(background, 3); + } else { + vipsBackground = vips_array_double_new(backgroundRGBA, 4); + } + + int code = vips_similarity(in, out, "scale", scale, "angle", angle, + "background", vipsBackground, "idx", idx, "idy", + idy, "odx", odx, "ody", ody, NULL); + + vips_area_unref(VIPS_AREA(vipsBackground)); + return code; +} + +int smartcrop(VipsImage *in, VipsImage **out, int width, int height, + int interesting) { + return vips_smartcrop(in, out, width, height, "interesting", interesting, + NULL); +} + +int crop(VipsImage *in, VipsImage **out, int left, int top, + int width, int height) { + // resolve image pages + int page_height = vips_image_get_page_height(in); + int n_pages = in->Ysize / page_height; + if (n_pages <= 1) { + return vips_crop(in, out, left, top, width, height, NULL); + } + + int in_width = in->Xsize; + VipsObject *base = VIPS_OBJECT(vips_image_new()); + VipsImage **page = (VipsImage **) vips_object_local_array(base, n_pages); + VipsImage **copy = (VipsImage **) vips_object_local_array(base, 1); + // split image into cropped frames + for (int i = 0; i < n_pages; i++) { + if ( + vips_extract_area(in, &page[i], 0, page_height * i, in_width, page_height, NULL) || + vips_crop(page[i], &page[i], left, top, width, height, NULL) + ) { + g_object_unref(base); + return -1; + } + } + + // reassemble frames and set page height + // copy before modifying metadata + if( + vips_arrayjoin(page, ©[0], n_pages, "across", 1, NULL) || + vips_copy(copy[0], out, NULL) + ) { + g_object_unref(base); + return -1; + } + vips_image_set_int(*out, VIPS_META_PAGE_HEIGHT, height); + g_object_unref(base); + return 0; +} + +int flatten_image(VipsImage *in, VipsImage **out, double r, double g, + double b) { + if (is_16bit(in->Type)) { + r = 65535 * r / 255; + g = 65535 * g / 255; + b = 65535 * b / 255; + } + + double background[3] = {r, g, b}; + VipsArrayDouble *vipsBackground = vips_array_double_new(background, 3); + + int code = vips_flatten(in, out, "background", vipsBackground, "max_alpha", + is_16bit(in->Type) ? 65535.0 : 255.0, NULL); + + vips_area_unref(VIPS_AREA(vipsBackground)); + return code; +} + +int is_16bit(VipsInterpretation interpretation) { + return interpretation == VIPS_INTERPRETATION_RGB16 || + interpretation == VIPS_INTERPRETATION_GREY16; +} + +int add_alpha(VipsImage *in, VipsImage **out) { + return vips_addalpha(in, out, NULL); +} + +int premultiply_alpha(VipsImage *in, VipsImage **out) { + return vips_premultiply(in, out, "max_alpha", max_alpha(in), NULL); +} + +int unpremultiply_alpha(VipsImage *in, VipsImage **out) { + return vips_unpremultiply(in, out, NULL); +} + +int cast(VipsImage *in, VipsImage **out, int bandFormat) { + return vips_cast(in, out, bandFormat, NULL); +} + +double max_alpha(VipsImage *in) { + switch (in->BandFmt) { + case VIPS_FORMAT_USHORT: + return 65535; + case VIPS_FORMAT_FLOAT: + case VIPS_FORMAT_DOUBLE: + return 1.0; + default: + return 255; + } +} + +int composite_image(VipsImage **in, VipsImage **out, int n, int *mode, int *x, + int *y) { + VipsArrayInt *xs = vips_array_int_new(x, n - 1); + VipsArrayInt *ys = vips_array_int_new(y, n - 1); + + int code = vips_composite(in, out, n, mode, "x", xs, "y", ys, NULL); + + vips_area_unref(VIPS_AREA(xs)); + vips_area_unref(VIPS_AREA(ys)); + return code; +} + +int composite2_image(VipsImage *base, VipsImage *overlay, VipsImage **out, + int mode, gint x, gint y) { + return vips_composite2(base, overlay, out, mode, "x", x, "y", y, NULL); +} + +int insert_image(VipsImage *main, VipsImage *sub, VipsImage **out, int x, int y, int expand, double r, double g, double b, double a) { + if (is_16bit(main->Type)) { + r = 65535 * r / 255; + g = 65535 * g / 255; + b = 65535 * b / 255; + a = 65535 * a / 255; + } + + double background[3] = {r, g, b}; + double backgroundRGBA[4] = {r, g, b, a}; + + VipsArrayDouble *vipsBackground; + + // Ignore the alpha channel if the image doesn't have one + if (main->Bands <= 3) { + vipsBackground = vips_array_double_new(background, 3); + } else { + vipsBackground = vips_array_double_new(backgroundRGBA, 4); + } + int code = vips_insert(main, sub, out, x, y, "expand", expand, "background", vipsBackground, NULL); + + vips_area_unref(VIPS_AREA(vipsBackground)); + + return code; +} + +int join(VipsImage *in1, VipsImage *in2, VipsImage **out, int direction) { + return vips_join(in1, in2, out, direction, NULL); +} + +int arrayjoin(VipsImage **in, VipsImage **out, int n, int across) { + return vips_arrayjoin(in, out, n, "across", across, NULL); +} + +int replicate(VipsImage *in, VipsImage **out, int across, int down) { + return vips_replicate(in, out, across, down, NULL); +} + +int grid(VipsImage *in, VipsImage **out, int tileHeight, int across, int down){ + return vips_grid(in, out, tileHeight, across, down, NULL); +} + +int adjust_gamma(VipsImage *in, VipsImage **out, double g) { + return vips_gamma(in, out, "exponent", g, NULL); +} diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/conversion.go b/vendor/github.com/davidbyttow/govips/v2/vips/conversion.go new file mode 100644 index 00000000000..0738727d9c7 --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/conversion.go @@ -0,0 +1,520 @@ +package vips + +// #cgo CFLAGS: -std=c99 +// #include "conversion.h" +import "C" + +// BandFormat represents VIPS_FORMAT type +type BandFormat int + +// BandFormat enum +const ( + BandFormatNotSet BandFormat = C.VIPS_FORMAT_NOTSET + BandFormatUchar BandFormat = C.VIPS_FORMAT_UCHAR + BandFormatChar BandFormat = C.VIPS_FORMAT_CHAR + BandFormatUshort BandFormat = C.VIPS_FORMAT_USHORT + BandFormatShort BandFormat = C.VIPS_FORMAT_SHORT + BandFormatUint BandFormat = C.VIPS_FORMAT_UINT + BandFormatInt BandFormat = C.VIPS_FORMAT_INT + BandFormatFloat BandFormat = C.VIPS_FORMAT_FLOAT + BandFormatComplex BandFormat = C.VIPS_FORMAT_COMPLEX + BandFormatDouble BandFormat = C.VIPS_FORMAT_DOUBLE + BandFormatDpComplex BandFormat = C.VIPS_FORMAT_DPCOMPLEX +) + +// BlendMode gives the various Porter-Duff and PDF blend modes. +// See https://libvips.github.io/libvips/API/current/libvips-conversion.html#VipsBlendMode +type BlendMode int + +// Constants define the various Porter-Duff and PDF blend modes. +// See https://libvips.github.io/libvips/API/current/libvips-conversion.html#VipsBlendMode +const ( + BlendModeClear BlendMode = C.VIPS_BLEND_MODE_CLEAR + BlendModeSource BlendMode = C.VIPS_BLEND_MODE_SOURCE + BlendModeOver BlendMode = C.VIPS_BLEND_MODE_OVER + BlendModeIn BlendMode = C.VIPS_BLEND_MODE_IN + BlendModeOut BlendMode = C.VIPS_BLEND_MODE_OUT + BlendModeAtop BlendMode = C.VIPS_BLEND_MODE_ATOP + BlendModeDest BlendMode = C.VIPS_BLEND_MODE_DEST + BlendModeDestOver BlendMode = C.VIPS_BLEND_MODE_DEST_OVER + BlendModeDestIn BlendMode = C.VIPS_BLEND_MODE_DEST_IN + BlendModeDestOut BlendMode = C.VIPS_BLEND_MODE_DEST_OUT + BlendModeDestAtop BlendMode = C.VIPS_BLEND_MODE_DEST_ATOP + BlendModeXOR BlendMode = C.VIPS_BLEND_MODE_XOR + BlendModeAdd BlendMode = C.VIPS_BLEND_MODE_ADD + BlendModeSaturate BlendMode = C.VIPS_BLEND_MODE_SATURATE + BlendModeMultiply BlendMode = C.VIPS_BLEND_MODE_MULTIPLY + BlendModeScreen BlendMode = C.VIPS_BLEND_MODE_SCREEN + BlendModeOverlay BlendMode = C.VIPS_BLEND_MODE_OVERLAY + BlendModeDarken BlendMode = C.VIPS_BLEND_MODE_DARKEN + BlendModeLighten BlendMode = C.VIPS_BLEND_MODE_LIGHTEN + BlendModeColorDodge BlendMode = C.VIPS_BLEND_MODE_COLOUR_DODGE + BlendModeColorBurn BlendMode = C.VIPS_BLEND_MODE_COLOUR_BURN + BlendModeHardLight BlendMode = C.VIPS_BLEND_MODE_HARD_LIGHT + BlendModeSoftLight BlendMode = C.VIPS_BLEND_MODE_SOFT_LIGHT + BlendModeDifference BlendMode = C.VIPS_BLEND_MODE_DIFFERENCE + BlendModeExclusion BlendMode = C.VIPS_BLEND_MODE_EXCLUSION +) + +// Direction represents VIPS_DIRECTION type +type Direction int + +// Direction enum +const ( + DirectionHorizontal Direction = C.VIPS_DIRECTION_HORIZONTAL + DirectionVertical Direction = C.VIPS_DIRECTION_VERTICAL +) + +// Angle represents VIPS_ANGLE type +type Angle int + +// Angle enum +const ( + Angle0 Angle = C.VIPS_ANGLE_D0 + Angle90 Angle = C.VIPS_ANGLE_D90 + Angle180 Angle = C.VIPS_ANGLE_D180 + Angle270 Angle = C.VIPS_ANGLE_D270 +) + +// Angle45 represents VIPS_ANGLE45 type +type Angle45 int + +// Angle45 enum +const ( + Angle45_0 Angle45 = C.VIPS_ANGLE45_D0 + Angle45_45 Angle45 = C.VIPS_ANGLE45_D45 + Angle45_90 Angle45 = C.VIPS_ANGLE45_D90 + Angle45_135 Angle45 = C.VIPS_ANGLE45_D135 + Angle45_180 Angle45 = C.VIPS_ANGLE45_D180 + Angle45_225 Angle45 = C.VIPS_ANGLE45_D225 + Angle45_270 Angle45 = C.VIPS_ANGLE45_D270 + Angle45_315 Angle45 = C.VIPS_ANGLE45_D315 +) + +// ExtendStrategy represents VIPS_EXTEND type +type ExtendStrategy int + +// ExtendStrategy enum +const ( + ExtendBlack ExtendStrategy = C.VIPS_EXTEND_BLACK + ExtendCopy ExtendStrategy = C.VIPS_EXTEND_COPY + ExtendRepeat ExtendStrategy = C.VIPS_EXTEND_REPEAT + ExtendMirror ExtendStrategy = C.VIPS_EXTEND_MIRROR + ExtendWhite ExtendStrategy = C.VIPS_EXTEND_WHITE + ExtendBackground ExtendStrategy = C.VIPS_EXTEND_BACKGROUND +) + +// Interesting represents VIPS_INTERESTING type +// https://libvips.github.io/libvips/API/current/libvips-conversion.html#VipsInteresting +type Interesting int + +// Interesting constants represent areas of interest which smart cropping will crop based on. +const ( + InterestingNone Interesting = C.VIPS_INTERESTING_NONE + InterestingCentre Interesting = C.VIPS_INTERESTING_CENTRE + InterestingEntropy Interesting = C.VIPS_INTERESTING_ENTROPY + InterestingAttention Interesting = C.VIPS_INTERESTING_ATTENTION + InterestingLow Interesting = C.VIPS_INTERESTING_LOW + InterestingHigh Interesting = C.VIPS_INTERESTING_HIGH + InterestingAll Interesting = C.VIPS_INTERESTING_ALL + InterestingLast Interesting = C.VIPS_INTERESTING_LAST +) + +func vipsCopyImageChangingInterpretation(in *C.VipsImage, interpretation Interpretation) (*C.VipsImage, error) { + var out *C.VipsImage + + if err := C.copy_image_changing_interpretation(in, &out, C.VipsInterpretation(interpretation)); int(err) != 0 { + return nil, handleImageError(out) + } + + return out, nil +} + +func vipsCopyImageChangingResolution(in *C.VipsImage, xres float64, yres float64) (*C.VipsImage, error) { + var out *C.VipsImage + + if err := C.copy_image_changing_resolution(in, &out, C.double(xres), C.double(yres)); int(err) != 0 { + return nil, handleImageError(out) + } + + return out, nil +} + +// https://libvips.github.io/libvips/API/current/libvips-conversion.html#vips-copy +func vipsCopyImage(in *C.VipsImage) (*C.VipsImage, error) { + var out *C.VipsImage + + if err := C.copy_image(in, &out); int(err) != 0 { + return nil, handleImageError(out) + } + + return out, nil +} + +// https://libvips.github.io/libvips/API/current/libvips-conversion.html#vips-embed +func vipsEmbed(in *C.VipsImage, left, top, width, height int, extend ExtendStrategy) (*C.VipsImage, error) { + incOpCounter("embed") + var out *C.VipsImage + + if err := C.embed_image(in, &out, C.int(left), C.int(top), C.int(width), C.int(height), C.int(extend)); err != 0 { + return nil, handleImageError(out) + } + + return out, nil +} + +// https://libvips.github.io/libvips/API/current/libvips-conversion.html#vips-embed +func vipsEmbedBackground(in *C.VipsImage, left, top, width, height int, backgroundColor *ColorRGBA) (*C.VipsImage, error) { + incOpCounter("embed") + var out *C.VipsImage + + if err := C.embed_image_background(in, &out, C.int(left), C.int(top), C.int(width), + C.int(height), C.double(backgroundColor.R), + C.double(backgroundColor.G), C.double(backgroundColor.B), C.double(backgroundColor.A)); err != 0 { + return nil, handleImageError(out) + } + + return out, nil +} + +func vipsEmbedMultiPage(in *C.VipsImage, left, top, width, height int, extend ExtendStrategy) (*C.VipsImage, error) { + incOpCounter("embedMultiPage") + var out *C.VipsImage + + if err := C.embed_multi_page_image(in, &out, C.int(left), C.int(top), C.int(width), C.int(height), C.int(extend)); err != 0 { + return nil, handleImageError(out) + } + + return out, nil +} + +func vipsEmbedMultiPageBackground(in *C.VipsImage, left, top, width, height int, backgroundColor *ColorRGBA) (*C.VipsImage, error) { + incOpCounter("embedMultiPageBackground") + var out *C.VipsImage + + if err := C.embed_multi_page_image_background(in, &out, C.int(left), C.int(top), C.int(width), + C.int(height), C.double(backgroundColor.R), + C.double(backgroundColor.G), C.double(backgroundColor.B), C.double(backgroundColor.A)); err != 0 { + return nil, handleImageError(out) + } + + return out, nil +} + +// https://libvips.github.io/libvips/API/current/libvips-conversion.html#vips-flip +func vipsFlip(in *C.VipsImage, direction Direction) (*C.VipsImage, error) { + incOpCounter("flip") + var out *C.VipsImage + + if err := C.flip_image(in, &out, C.int(direction)); err != 0 { + return nil, handleImageError(out) + } + + return out, nil +} + +// https://libvips.github.io/libvips/API/current/libvips-conversion.html#vips-recomb +func vipsRecomb(in *C.VipsImage, m *C.VipsImage) (*C.VipsImage, error) { + incOpCounter("recomb") + var out *C.VipsImage + + if err := C.recomb_image(in, &out, m); err != 0 { + return nil, handleImageError(out) + } + + return out, nil +} + +// https://libvips.github.io/libvips/API/current/libvips-conversion.html#vips-extract-area +func vipsExtractArea(in *C.VipsImage, left, top, width, height int) (*C.VipsImage, error) { + incOpCounter("extractArea") + var out *C.VipsImage + + if err := C.extract_image_area(in, &out, C.int(left), C.int(top), C.int(width), C.int(height)); err != 0 { + return nil, handleImageError(out) + } + + return out, nil +} + +func vipsExtractAreaMultiPage(in *C.VipsImage, left, top, width, height int) (*C.VipsImage, error) { + incOpCounter("extractAreaMultiPage") + var out *C.VipsImage + + if err := C.extract_area_multi_page(in, &out, C.int(left), C.int(top), C.int(width), C.int(height)); err != 0 { + return nil, handleImageError(out) + } + + return out, nil +} + +// https://libvips.github.io/libvips/API/current/libvips-conversion.html#vips-extract-band +func vipsExtractBand(in *C.VipsImage, band, num int) (*C.VipsImage, error) { + incOpCounter("extractBand") + var out *C.VipsImage + + if err := C.extract_band(in, &out, C.int(band), C.int(num)); err != 0 { + return nil, handleImageError(out) + } + + return out, nil +} + +// http://libvips.github.io/libvips/API/current/libvips-resample.html#vips-similarity +func vipsSimilarity(in *C.VipsImage, scale float64, angle float64, color *ColorRGBA, + idx float64, idy float64, odx float64, ody float64) (*C.VipsImage, error) { + incOpCounter("similarity") + var out *C.VipsImage + + if err := C.similarity(in, &out, C.double(scale), C.double(angle), + C.double(color.R), C.double(color.G), C.double(color.B), C.double(color.A), + C.double(idx), C.double(idy), C.double(odx), C.double(ody)); err != 0 { + return nil, handleImageError(out) + } + + return out, nil +} + +// http://libvips.github.io/libvips/API/current/libvips-conversion.html#vips-smartcrop +func vipsSmartCrop(in *C.VipsImage, width int, height int, interesting Interesting) (*C.VipsImage, error) { + incOpCounter("smartcrop") + var out *C.VipsImage + + if err := C.smartcrop(in, &out, C.int(width), C.int(height), C.int(interesting)); err != 0 { + return nil, handleImageError(out) + } + + return out, nil +} + +// http://libvips.github.io/libvips/API/current/libvips-conversion.html#vips-crop +func vipsCrop(in *C.VipsImage, left int, top int, width int, height int) (*C.VipsImage, error) { + incOpCounter("crop") + var out *C.VipsImage + + if err := C.crop(in, &out, C.int(left), C.int(top), C.int(width), C.int(height)); err != 0 { + return nil, handleImageError(out) + } + + return out, nil +} + +// https://libvips.github.io/libvips/API/current/libvips-conversion.html#vips-rot +func vipsRotate(in *C.VipsImage, angle Angle) (*C.VipsImage, error) { + incOpCounter("rot") + var out *C.VipsImage + + if err := C.rot_image(in, &out, C.VipsAngle(angle)); err != 0 { + return nil, handleImageError(out) + } + + return out, nil +} + +// https://libvips.github.io/libvips/API/current/libvips-conversion.html#vips-autorot +func vipsAutoRotate(in *C.VipsImage) (*C.VipsImage, error) { + incOpCounter("autorot") + var out *C.VipsImage + + if err := C.autorot_image(in, &out); err != 0 { + return nil, handleImageError(out) + } + + return out, nil +} + +// https://libvips.github.io/libvips/API/current/libvips-conversion.html#vips-zoom +func vipsZoom(in *C.VipsImage, xFactor, yFactor int) (*C.VipsImage, error) { + incOpCounter("zoom") + var out *C.VipsImage + + if err := C.zoom_image(in, &out, C.int(xFactor), C.int(yFactor)); err != 0 { + return nil, handleImageError(out) + } + + return out, nil +} + +// https://libvips.github.io/libvips/API/current/libvips-conversion.html#vips-bandjoin +func vipsBandJoin(ins []*C.VipsImage) (*C.VipsImage, error) { + incOpCounter("bandjoin") + var out *C.VipsImage + + if err := C.bandjoin(&ins[0], &out, C.int(len(ins))); err != 0 { + return nil, handleImageError(out) + } + + return out, nil +} + +// http://libvips.github.io/libvips/API/current/libvips-conversion.html#vips-bandjoin-const +func vipsBandJoinConst(in *C.VipsImage, constants []float64) (*C.VipsImage, error) { + incOpCounter("bandjoin_const") + var out *C.VipsImage + + if err := C.bandjoin_const(in, &out, (*C.double)(&constants[0]), C.int(len(constants))); err != 0 { + return nil, handleImageError(out) + } + + return out, nil +} + +// https://libvips.github.io/libvips/API/current/libvips-conversion.html#vips-flatten +func vipsFlatten(in *C.VipsImage, color *Color) (*C.VipsImage, error) { + incOpCounter("flatten") + var out *C.VipsImage + + err := C.flatten_image(in, &out, C.double(color.R), C.double(color.G), C.double(color.B)) + if int(err) != 0 { + return nil, handleImageError(out) + } + + return out, nil +} + +func vipsAddAlpha(in *C.VipsImage) (*C.VipsImage, error) { + incOpCounter("addAlpha") + var out *C.VipsImage + + if err := C.add_alpha(in, &out); err != 0 { + return nil, handleImageError(out) + } + + return out, nil +} + +// https://libvips.github.io/libvips/API/current/libvips-conversion.html#vips-premultiply +func vipsPremultiplyAlpha(in *C.VipsImage) (*C.VipsImage, error) { + incOpCounter("premultiplyAlpha") + var out *C.VipsImage + + if err := C.premultiply_alpha(in, &out); err != 0 { + return nil, handleImageError(out) + } + + return out, nil +} + +// https://libvips.github.io/libvips/API/current/libvips-conversion.html#vips-unpremultiply +func vipsUnpremultiplyAlpha(in *C.VipsImage) (*C.VipsImage, error) { + incOpCounter("unpremultiplyAlpha") + var out *C.VipsImage + + if err := C.unpremultiply_alpha(in, &out); err != 0 { + return nil, handleImageError(out) + } + + return out, nil +} + +func vipsCast(in *C.VipsImage, bandFormat BandFormat) (*C.VipsImage, error) { + incOpCounter("cast") + var out *C.VipsImage + + if err := C.cast(in, &out, C.int(bandFormat)); err != 0 { + return nil, handleImageError(out) + } + + return out, nil +} + +// https://libvips.github.io/libvips/API/current/libvips-conversion.html#vips-composite +func vipsComposite(ins []*C.VipsImage, modes []C.int, xs, ys []C.int) (*C.VipsImage, error) { + incOpCounter("composite_multi") + var out *C.VipsImage + + if err := C.composite_image(&ins[0], &out, C.int(len(ins)), &modes[0], &xs[0], &ys[0]); err != 0 { + return nil, handleImageError(out) + } + + return out, nil +} + +// https://libvips.github.io/libvips/API/current/libvips-conversion.html#vips-composite2 +func vipsComposite2(base *C.VipsImage, overlay *C.VipsImage, mode BlendMode, x, y int) (*C.VipsImage, error) { + incOpCounter("composite") + var out *C.VipsImage + + if err := C.composite2_image(base, overlay, &out, C.int(mode), C.gint(x), C.gint(y)); err != 0 { + return nil, handleImageError(out) + } + + return out, nil +} + +func vipsInsert(main *C.VipsImage, sub *C.VipsImage, x, y int, expand bool, background *ColorRGBA) (*C.VipsImage, error) { + incOpCounter("insert") + var out *C.VipsImage + + if background == nil { + background = &ColorRGBA{R: 0.0, G: 0.0, B: 0.0, A: 255.0} + } + + expandInt := 0 + if expand { + expandInt = 1 + } + + if err := C.insert_image(main, sub, &out, C.int(x), C.int(y), C.int(expandInt), C.double(background.R), C.double(background.G), C.double(background.B), C.double(background.A)); err != 0 { + return nil, handleImageError(out) + } + + return out, nil +} + +// https://libvips.github.io/libvips/API/current/libvips-conversion.html#vips-join +func vipsJoin(input1 *C.VipsImage, input2 *C.VipsImage, dir Direction) (*C.VipsImage, error) { + incOpCounter("join") + var out *C.VipsImage + + defer C.g_object_unref(C.gpointer(input1)) + defer C.g_object_unref(C.gpointer(input2)) + if err := C.join(input1, input2, &out, C.int(dir)); err != 0 { + return nil, handleVipsError() + } + return out, nil +} + +// https://libvips.github.io/libvips/API/current/libvips-conversion.html#vips-arrayjoin +func vipsArrayJoin(inputs []*C.VipsImage, across int) (*C.VipsImage, error) { + incOpCounter("arrayjoin") + var out *C.VipsImage + + if err := C.arrayjoin(&inputs[0], &out, C.int(len(inputs)), C.int(across)); err != 0 { + return nil, handleVipsError() + } + return out, nil +} + +// https://www.libvips.org/API/current/libvips-conversion.html#vips-replicate +func vipsReplicate(in *C.VipsImage, across int, down int) (*C.VipsImage, error) { + incOpCounter("replicate") + var out *C.VipsImage + + if err := C.replicate(in, &out, C.int(across), C.int(down)); err != 0 { + return nil, handleImageError(out) + } + return out, nil +} + +// https://www.libvips.org/API/current/libvips-conversion.html#vips-grid +func vipsGrid(in *C.VipsImage, tileHeight, across, down int) (*C.VipsImage, error) { + incOpCounter("grid") + var out *C.VipsImage + + if err := C.grid(in, &out, C.int(tileHeight), C.int(across), C.int(down)); err != 0 { + return nil, handleImageError(out) + } + return out, nil +} + +func vipsGamma(image *C.VipsImage, gamma float64) (*C.VipsImage, error) { + incOpCounter("gamma") + var out *C.VipsImage + + if err := C.adjust_gamma(image, &out, C.double(gamma)); err != 0 { + return nil, handleImageError(out) + } + + return out, nil +} diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/conversion.h b/vendor/github.com/davidbyttow/govips/v2/vips/conversion.h new file mode 100644 index 00000000000..ac92c5965c1 --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/conversion.h @@ -0,0 +1,70 @@ +// https://libvips.github.io/libvips/API/current/libvips-conversion.html + +#include +#include + +int copy_image_changing_interpretation(VipsImage *in, VipsImage **out, + VipsInterpretation interpretation); +int copy_image_changing_resolution(VipsImage *in, VipsImage **out, double xres, + double yres); +int copy_image(VipsImage *in, VipsImage **out); + +int embed_image(VipsImage *in, VipsImage **out, int left, int top, int width, + int height, int extend); +int embed_image_background(VipsImage *in, VipsImage **out, int left, int top, int width, + int height, double r, double g, double b, double a); +int embed_multi_page_image(VipsImage *in, VipsImage **out, int left, int top, int width, + int height, int extend); +int embed_multi_page_image_background(VipsImage *in, VipsImage **out, int left, int top, + int width, int height, double r, double g, double b, double a); + +int flip_image(VipsImage *in, VipsImage **out, int direction); + +int recomb_image(VipsImage *in, VipsImage **out, VipsImage *m); + +int extract_image_area(VipsImage *in, VipsImage **out, int left, int top, + int width, int height); +int extract_area_multi_page(VipsImage *in, VipsImage **out, int left, int top, + int width, int height); + +int extract_band(VipsImage *in, VipsImage **out, int band, int num); + +int rot_image(VipsImage *in, VipsImage **out, VipsAngle angle); +int autorot_image(VipsImage *in, VipsImage **out); + +int zoom_image(VipsImage *in, VipsImage **out, int xfac, int yfac); +int smartcrop(VipsImage *in, VipsImage **out, int width, int height, + int interesting); +int crop(VipsImage *in, VipsImage **out, int left, int top, + int width, int height); + +int bandjoin(VipsImage **in, VipsImage **out, int n); +int bandjoin_const(VipsImage *in, VipsImage **out, double constants[], int n); +int similarity(VipsImage *in, VipsImage **out, double scale, double angle, + double r, double g, double b, double a, double idx, double idy, + double odx, double ody); +int flatten_image(VipsImage *in, VipsImage **out, double r, double g, double b); +int add_alpha(VipsImage *in, VipsImage **out); +int premultiply_alpha(VipsImage *in, VipsImage **out); +int unpremultiply_alpha(VipsImage *in, VipsImage **out); +int cast(VipsImage *in, VipsImage **out, int bandFormat); +double max_alpha(VipsImage *in); + +int composite_image(VipsImage **in, VipsImage **out, int n, int *mode, int *x, + int *y); +int composite2_image(VipsImage *base, VipsImage *overlay, VipsImage **out, + int mode, gint x, gint y); + +int insert_image(VipsImage *main, VipsImage *sub, VipsImage **out, int x, int y, + int expand, double r, double g, double b, double a); + +int join(VipsImage *in1, VipsImage *in2, VipsImage **out, int direction); +int arrayjoin(VipsImage **in, VipsImage **out, int n, int across); + +int is_16bit(VipsInterpretation interpretation); + +int replicate(VipsImage *in, VipsImage **out, int across, int down); + +int grid(VipsImage *in, VipsImage **out, int tileHeight, int across, int down); + +int adjust_gamma(VipsImage *in, VipsImage **out, double g); diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/convolution.c b/vendor/github.com/davidbyttow/govips/v2/vips/convolution.c new file mode 100644 index 00000000000..48a9472bb76 --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/convolution.c @@ -0,0 +1,14 @@ +#include "convolution.h" + +int gaussian_blur_image(VipsImage *in, VipsImage **out, double sigma, double min_ampl) { + return vips_gaussblur(in, out, sigma, "min_ampl", min_ampl, NULL); +} + +int sharpen_image(VipsImage *in, VipsImage **out, double sigma, double x1, + double m2) { + return vips_sharpen(in, out, "sigma", sigma, "x1", x1, "m2", m2, NULL); +} + +int sobel_image(VipsImage *in, VipsImage **out) { + return vips_sobel(in, out, NULL); +} diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/convolution.go b/vendor/github.com/davidbyttow/govips/v2/vips/convolution.go new file mode 100644 index 00000000000..23622767fca --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/convolution.go @@ -0,0 +1,40 @@ +package vips + +// #include "convolution.h" +import "C" + +// https://libvips.github.io/libvips/API/current/libvips-convolution.html#vips-gaussblur +func vipsGaussianBlur(in *C.VipsImage, sigma, minAmpl float64) (*C.VipsImage, error) { + incOpCounter("gaussblur") + var out *C.VipsImage + + if err := C.gaussian_blur_image(in, &out, C.double(sigma), C.double(minAmpl)); err != 0 { + return nil, handleImageError(out) + } + + return out, nil +} + +// https://libvips.github.io/libvips/API/current/libvips-convolution.html#vips-sharpen +func vipsSharpen(in *C.VipsImage, sigma float64, x1 float64, m2 float64) (*C.VipsImage, error) { + incOpCounter("sharpen") + var out *C.VipsImage + + if err := C.sharpen_image(in, &out, C.double(sigma), C.double(x1), C.double(m2)); err != 0 { + return nil, handleImageError(out) + } + + return out, nil +} + +// https://libvips.github.io/libvips/API/current/libvips-convolution.html#vips-sobel +func vipsSobel(in *C.VipsImage) (*C.VipsImage, error) { + incOpCounter("sobel") + var out *C.VipsImage + + if err := C.sobel_image(in, &out); err != 0 { + return nil, handleImageError(out) + } + + return out, nil +} diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/convolution.h b/vendor/github.com/davidbyttow/govips/v2/vips/convolution.h new file mode 100644 index 00000000000..30a525ee822 --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/convolution.h @@ -0,0 +1,9 @@ +// https://libvips.github.io/libvips/API/current/libvips-convolution.html + +#include +#include + +int gaussian_blur_image(VipsImage *in, VipsImage **out, double sigma, double min_ampl); +int sharpen_image(VipsImage *in, VipsImage **out, double sigma, double x1, + double m2); +int sobel_image(VipsImage *in, VipsImage **out); diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/create.c b/vendor/github.com/davidbyttow/govips/v2/vips/create.c new file mode 100644 index 00000000000..1ffb7a34c15 --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/create.c @@ -0,0 +1,24 @@ +// clang-format off +// include order matters +#include "lang.h" +#include "create.h" +// clang-format on + +// https://libvips.github.io/libvips/API/current/libvips-create.html#vips-xyz +int xyz(VipsImage **out, int width, int height) { + return vips_xyz(out, width, height, NULL); +} + +// http://libvips.github.io/libvips/API/current/libvips-create.html#vips-black +int black(VipsImage **out, int width, int height) { + return vips_black(out, width, height, NULL); +} + +// https://libvips.github.io/libvips/API/current/libvips-create.html#vips-identity +int identity(VipsImage **out, int ushort) { + if (ushort > 0) { + return vips_identity(out, "ushort", TRUE, NULL); + } else { + return vips_identity(out, NULL); + } +} diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/create.go b/vendor/github.com/davidbyttow/govips/v2/vips/create.go new file mode 100644 index 00000000000..3532cf535e2 --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/create.go @@ -0,0 +1,37 @@ +package vips + +// #include "create.h" +import "C" + +// https://libvips.github.io/libvips/API/current/libvips-create.html#vips-xyz +func vipsXYZ(width int, height int) (*C.VipsImage, error) { + var out *C.VipsImage + + if err := C.xyz(&out, C.int(width), C.int(height)); err != 0 { + return nil, handleImageError(out) + } + + return out, nil +} + +// http://libvips.github.io/libvips/API/current/libvips-create.html#vips-black +func vipsBlack(width int, height int) (*C.VipsImage, error) { + var out *C.VipsImage + + if err := C.black(&out, C.int(width), C.int(height)); err != 0 { + return nil, handleImageError(out) + } + + return out, nil +} + +// https://libvips.github.io/libvips/API/current/libvips-create.html#vips-identity +func vipsIdentity(ushort bool) (*C.VipsImage, error) { + var out *C.VipsImage + ushortInt := C.int(boolToInt(ushort)) + if err := C.identity(&out, ushortInt); err != 0 { + return nil, handleImageError(out) + } + + return out, nil +} diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/create.h b/vendor/github.com/davidbyttow/govips/v2/vips/create.h new file mode 100644 index 00000000000..245d11fbaf4 --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/create.h @@ -0,0 +1,12 @@ +// https://libvips.github.io/libvips/API/current/libvips-create.html + +// clang-format off +// include order matters +#include +#include +#include +// clang-format on + +int xyz(VipsImage **out, int width, int height); +int black(VipsImage **out, int width, int height); +int identity(VipsImage **out, int ushort); diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/draw.c b/vendor/github.com/davidbyttow/govips/v2/vips/draw.c new file mode 100644 index 00000000000..024e18c1c02 --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/draw.c @@ -0,0 +1,24 @@ +#include "draw.h" + +#include "conversion.h" + +int draw_rect(VipsImage *in, double r, double g, double b, double a, int left, + int top, int width, int height, int fill) { + if (is_16bit(in->Type)) { + r = 65535 * r / 255; + g = 65535 * g / 255; + b = 65535 * b / 255; + a = 65535 * a / 255; + } + + double background[3] = {r, g, b}; + double backgroundRGBA[4] = {r, g, b, a}; + + if (in->Bands <= 3) { + return vips_draw_rect(in, background, 3, left, top, width, height, "fill", + fill, NULL); + } else { + return vips_draw_rect(in, backgroundRGBA, 4, left, top, width, height, + "fill", fill, NULL); + } +} diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/draw.go b/vendor/github.com/davidbyttow/govips/v2/vips/draw.go new file mode 100644 index 00000000000..d2ab2ad9ee3 --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/draw.go @@ -0,0 +1,21 @@ +package vips + +// #include "draw.h" +import "C" + +// https://libvips.github.io/libvips/API/current/libvips-draw.html#vips-draw-rect +func vipsDrawRect(in *C.VipsImage, color ColorRGBA, left int, top int, width int, height int, fill bool) error { + incOpCounter("draw_rect") + + fillBit := 0 + if fill { + fillBit = 1 + } + + if err := C.draw_rect(in, C.double(color.R), C.double(color.G), C.double(color.B), C.double(color.A), + C.int(left), C.int(top), C.int(width), C.int(height), C.int(fillBit)); err != 0 { + return handleImageError(in) + } + + return nil +} diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/draw.h b/vendor/github.com/davidbyttow/govips/v2/vips/draw.h new file mode 100644 index 00000000000..a1c5b6f9646 --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/draw.h @@ -0,0 +1,7 @@ +// https://libvips.github.io/libvips/API/current/libvips-draw.html + +#include +#include + +int draw_rect(VipsImage *in, double r, double g, double b, double a, int left, + int top, int width, int height, int fill); diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/error.go b/vendor/github.com/davidbyttow/govips/v2/vips/error.go new file mode 100644 index 00000000000..08eb41e78f3 --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/error.go @@ -0,0 +1,39 @@ +package vips + +// #include +import "C" + +import ( + "errors" + "fmt" + dbg "runtime/debug" + "unsafe" +) + +var ( + // ErrUnsupportedImageFormat when image type is unsupported + ErrUnsupportedImageFormat = errors.New("unsupported image format") +) + +func handleImageError(out *C.VipsImage) error { + if out != nil { + clearImage(out) + } + + return handleVipsError() +} + +func handleSaveBufferError(out unsafe.Pointer) error { + if out != nil { + gFreePointer(out) + } + + return handleVipsError() +} + +func handleVipsError() error { + s := C.GoString(C.vips_error_buffer()) + C.vips_error_clear() + + return fmt.Errorf("%v\nStack:\n%s", s, dbg.Stack()) +} diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/foreign.c b/vendor/github.com/davidbyttow/govips/v2/vips/foreign.c new file mode 100644 index 00000000000..23f9d27d544 --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/foreign.c @@ -0,0 +1,578 @@ +#include "foreign.h" + +#include "lang.h" + +void set_bool_param(Param *p, gboolean b) { + p->type = PARAM_TYPE_BOOL; + p->value.b = b; + p->is_set = TRUE; +} + +void set_int_param(Param *p, gint i) { + p->type = PARAM_TYPE_INT; + p->value.i = i; + p->is_set = TRUE; +} + +void set_double_param(Param *p, gdouble d) { + p->type = PARAM_TYPE_DOUBLE; + p->value.d = d; + p->is_set = TRUE; +} + +int load_image_buffer(LoadParams *params, void *buf, size_t len, + VipsImage **out) { + int code = 1; + ImageType imageType = params->inputFormat; + + if (imageType == JPEG) { + // shrink: int, fail: bool, autorotate: bool + code = vips_jpegload_buffer(buf, len, out, "fail", params->fail, + "autorotate", params->autorotate, "shrink", + params->jpegShrink, NULL); + } else if (imageType == PNG) { + code = vips_pngload_buffer(buf, len, out, NULL); + } else if (imageType == WEBP) { + // page: int, n: int, scale: double + code = vips_webpload_buffer(buf, len, out, "page", params->page, "n", + params->n, NULL); + } else if (imageType == TIFF) { + // page: int, n: int, autorotate: bool, subifd: int + code = + vips_tiffload_buffer(buf, len, out, "page", params->page, "n", + params->n, "autorotate", params->autorotate, NULL); + } else if (imageType == GIF) { + // page: int, n: int, scale: double + code = vips_gifload_buffer(buf, len, out, "page", params->page, "n", + params->n, NULL); + } else if (imageType == PDF) { + // page: int, n: int, dpi: double, scale: double, background: color + code = vips_pdfload_buffer(buf, len, out, "page", params->page, "n", + params->n, "dpi", params->dpi, NULL); + } else if (imageType == SVG) { + // dpi: double, scale: double, unlimited: bool + code = vips_svgload_buffer(buf, len, out, "dpi", params->dpi, "unlimited", + params->svgUnlimited, NULL); + } else if (imageType == HEIF) { + // added autorotate on load as currently it addresses orientation issues + // https://github.com/libvips/libvips/pull/1680 + // page: int, n: int, thumbnail: bool + code = vips_heifload_buffer(buf, len, out, "page", params->page, "n", + params->n, "thumbnail", params->heifThumbnail, + "autorotate", TRUE, NULL); + } else if (imageType == MAGICK) { + // page: int, n: int, density: string + code = vips_magickload_buffer(buf, len, out, "page", params->page, "n", + params->n, NULL); + } else if (imageType == AVIF) { + code = vips_heifload_buffer(buf, len, out, "page", params->page, "n", + params->n, "thumbnail", params->heifThumbnail, + "autorotate", params->autorotate, NULL); + + } + #if (VIPS_MAJOR_VERSION >= 8) && (VIPS_MINOR_VERSION >= 11) + else if (imageType == JP2K) { + code = vips_jp2kload_buffer(buf, len, out, "page", params->page, NULL); + } + #endif + + return code; +} + +#define MAYBE_SET_BOOL(OP, PARAM, NAME) \ + if (PARAM.is_set) { \ + vips_object_set(VIPS_OBJECT(OP), NAME, PARAM.value.b, NULL); \ + } + +#define MAYBE_SET_INT(OP, PARAM, NAME) \ + if (PARAM.is_set) { \ + vips_object_set(VIPS_OBJECT(OP), NAME, PARAM.value.i, NULL); \ + } + +#define MAYBE_SET_DOUBLE(OP, PARAM, NAME) \ + if (PARAM.is_set) { \ + vips_object_set(VIPS_OBJECT(OP), NAME, PARAM.value.d, NULL); \ + } + +typedef int (*SetLoadOptionsFn)(VipsOperation *operation, LoadParams *params); + +int set_jpegload_options(VipsOperation *operation, LoadParams *params) { + MAYBE_SET_BOOL(operation, params->autorotate, "autorotate"); + MAYBE_SET_BOOL(operation, params->fail, "fail"); + MAYBE_SET_INT(operation, params->jpegShrink, "shrink"); + return 0; +} + +int set_pngload_options(VipsOperation *operation, LoadParams *params) { + MAYBE_SET_BOOL(operation, params->fail, "fail"); + return 0; +} + +int set_webpload_options(VipsOperation *operation, LoadParams *params) { + MAYBE_SET_INT(operation, params->page, "page"); + MAYBE_SET_INT(operation, params->n, "n"); + return 0; +} + +int set_tiffload_options(VipsOperation *operation, LoadParams *params) { + MAYBE_SET_BOOL(operation, params->autorotate, "autorotate"); + MAYBE_SET_INT(operation, params->page, "page"); + MAYBE_SET_INT(operation, params->n, "n"); + return 0; +} + +int set_gifload_options(VipsOperation *operation, LoadParams *params) { + MAYBE_SET_INT(operation, params->page, "page"); + MAYBE_SET_INT(operation, params->n, "n"); + return 0; +} + +int set_pdfload_options(VipsOperation *operation, LoadParams *params) { + MAYBE_SET_INT(operation, params->page, "page"); + MAYBE_SET_INT(operation, params->n, "n"); + MAYBE_SET_DOUBLE(operation, params->dpi, "dpi"); + return 0; +} + +int set_svgload_options(VipsOperation *operation, LoadParams *params) { + MAYBE_SET_BOOL(operation, params->svgUnlimited, "unlimited"); + MAYBE_SET_DOUBLE(operation, params->dpi, "dpi"); + return 0; +} + +int set_heifload_options(VipsOperation *operation, LoadParams *params) { + MAYBE_SET_BOOL(operation, params->autorotate, "autorotate"); + MAYBE_SET_BOOL(operation, params->heifThumbnail, "thumbnail"); + MAYBE_SET_INT(operation, params->page, "page"); + MAYBE_SET_INT(operation, params->n, "n"); + return 0; +} + +int set_jp2kload_options(VipsOperation *operation, LoadParams *params) { + MAYBE_SET_INT(operation, params->page, "page"); + return 0; +} + +int set_jxlload_options(VipsOperation *operation, LoadParams *params) { + // nothing need to do + return 0; +} + +int set_magickload_options(VipsOperation *operation, LoadParams *params) { + MAYBE_SET_INT(operation, params->page, "page"); + MAYBE_SET_INT(operation, params->n, "n"); + return 0; +} + +int load_buffer(const char *operationName, void *buf, size_t len, + LoadParams *params, SetLoadOptionsFn setLoadOptions) { + VipsBlob *blob = vips_blob_new(NULL, buf, len); + + VipsOperation *operation = vips_operation_new(operationName); + if (!operation) { + return 1; + } + + if (vips_object_set(VIPS_OBJECT(operation), "buffer", blob, NULL)) { + vips_area_unref(VIPS_AREA(blob)); + return 1; + } + + vips_area_unref(VIPS_AREA(blob)); + + if (setLoadOptions(operation, params)) { + vips_object_unref_outputs(VIPS_OBJECT(operation)); + g_object_unref(operation); + return 1; + } + + if (vips_cache_operation_buildp(&operation)) { + vips_object_unref_outputs(VIPS_OBJECT(operation)); + g_object_unref(operation); + return 1; + } + + g_object_get(VIPS_OBJECT(operation), "out", ¶ms->outputImage, NULL); + + vips_object_unref_outputs(VIPS_OBJECT(operation)); + g_object_unref(operation); + + return 0; +} + +typedef int (*SetSaveOptionsFn)(VipsOperation *operation, SaveParams *params); + +int save_buffer(const char *operationName, SaveParams *params, + SetSaveOptionsFn setSaveOptions) { + VipsBlob *blob; + VipsOperation *operation = vips_operation_new(operationName); + if (!operation) { + return 1; + } + + if (vips_object_set(VIPS_OBJECT(operation), "in", params->inputImage, NULL)) { + return 1; + } + + if (setSaveOptions(operation, params)) { + g_object_unref(operation); + return 1; + } + + if (vips_cache_operation_buildp(&operation)) { + vips_object_unref_outputs(VIPS_OBJECT(operation)); + g_object_unref(operation); + return 1; + } + + g_object_get(VIPS_OBJECT(operation), "buffer", &blob, NULL); + g_object_unref(operation); + + VipsArea *area = VIPS_AREA(blob); + + params->outputBuffer = (char *)(area->data); + params->outputLen = area->length; + area->free_fn = NULL; + vips_area_unref(area); + + return 0; +} + +// https://libvips.github.io/libvips/API/current/VipsForeignSave.html#vips-jpegsave-buffer +int set_jpegsave_options(VipsOperation *operation, SaveParams *params) { + int ret = vips_object_set( + VIPS_OBJECT(operation), "strip", params->stripMetadata, "optimize_coding", + params->jpegOptimizeCoding, "interlace", params->interlace, + "subsample_mode", params->jpegSubsample, "trellis_quant", + params->jpegTrellisQuant, "overshoot_deringing", + params->jpegOvershootDeringing, "optimize_scans", + params->jpegOptimizeScans, "quant_table", params->jpegQuantTable, NULL); + + if (!ret && params->quality) { + ret = vips_object_set(VIPS_OBJECT(operation), "Q", params->quality, NULL); + } + + return ret; +} + +// https://libvips.github.io/libvips/API/current/VipsForeignSave.html#vips-pngsave-buffer +int set_pngsave_options(VipsOperation *operation, SaveParams *params) { + int ret = + vips_object_set(VIPS_OBJECT(operation), "strip", params->stripMetadata, + "compression", params->pngCompression, "interlace", + params->interlace, "filter", params->pngFilter, "palette", + params->pngPalette, NULL); + + if (!ret && params->quality) { + ret = vips_object_set(VIPS_OBJECT(operation), "Q", params->quality, NULL); + } + + if (!ret && params->pngDither) { + ret = vips_object_set(VIPS_OBJECT(operation), "dither", params->pngDither, NULL); + } + + if (!ret && params->pngBitdepth) { + ret = vips_object_set(VIPS_OBJECT(operation), "bitdepth", params->pngBitdepth, NULL); + } + + // TODO: Handle `profile` param. + + return ret; +} + +// https://github.com/libvips/libvips/blob/master/libvips/foreign/webpsave.c#L524 +// https://libvips.github.io/libvips/API/current/VipsForeignSave.html#vips-webpsave-buffer +int set_webpsave_options(VipsOperation *operation, SaveParams *params) { + int ret = + vips_object_set(VIPS_OBJECT(operation), + "strip", params->stripMetadata, + "lossless", params->webpLossless, + "near_lossless", params->webpNearLossless, + "reduction_effort", params->webpReductionEffort, + "profile", params->webpIccProfile ? params->webpIccProfile : "none", + "min_size", params->webpMinSize, + "kmin", params->webpKMin, + "kmax", params->webpKMax, + NULL); + + if (!ret && params->quality) { + ret = vips_object_set(VIPS_OBJECT(operation), "Q", params->quality, NULL); + } + + return ret; +} + +// https://libvips.github.io/libvips/API/current/VipsForeignSave.html#vips-tiffsave-buffer +int set_tiffsave_options(VipsOperation *operation, SaveParams *params) { + int ret = vips_object_set( + VIPS_OBJECT(operation), "strip", params->stripMetadata, "compression", + params->tiffCompression, "predictor", params->tiffPredictor, "pyramid", + params->tiffPyramid, "tile_height", params->tiffTileHeight, "tile_width", + params->tiffTileWidth, "tile", params->tiffTile, NULL); + + if (!ret && params->quality) { + ret = vips_object_set(VIPS_OBJECT(operation), "Q", params->quality, NULL); + } + + return ret; +} + +// https://libvips.github.io/libvips/API/current/VipsForeignSave.html#vips-magicksave-buffer +int set_magicksave_options(VipsOperation *operation, SaveParams *params) { + int ret = vips_object_set(VIPS_OBJECT(operation), "format", "GIF", "bitdepth", params->gifBitdepth, NULL); + + if (!ret && params->quality) { + ret = vips_object_set(VIPS_OBJECT(operation), "quality", params->quality, + NULL); + } + return ret; +} + +// https://libvips.github.io/libvips/API/current/VipsForeignSave.html#vips-gifsave-buffer +int set_gifsave_options(VipsOperation *operation, SaveParams *params) { + int ret = 0; + // See for argument values: https://www.libvips.org/API/current/VipsForeignSave.html#vips-gifsave + if (params->gifDither > 0.0 && params->gifDither <= 10) { + ret = vips_object_set(VIPS_OBJECT(operation), "dither", params->gifDither, NULL); + } + if (params->gifEffort >= 1 && params->gifEffort <= 10) { + ret = vips_object_set(VIPS_OBJECT(operation), "effort", params->gifEffort, NULL); + } + if (params->gifBitdepth >= 1 && params->gifBitdepth <= 8) { + ret = vips_object_set(VIPS_OBJECT(operation), "bitdepth", params->gifBitdepth, NULL); + } + return ret; +} + +// https://github.com/libvips/libvips/blob/master/libvips/foreign/heifsave.c#L653 +int set_heifsave_options(VipsOperation *operation, SaveParams *params) { + int ret = vips_object_set(VIPS_OBJECT(operation), "lossless", + params->heifLossless, NULL); + +#if (VIPS_MAJOR_VERSION >= 8) && (VIPS_MINOR_VERSION >= 13) + if (!ret && params->heifBitdepth && params->heifEffort) { + ret = vips_object_set(VIPS_OBJECT(operation), "bitdepth", + params->heifBitdepth, "effort", params->heifEffort, + NULL); + } +#else + if (!ret && params->heifEffort) { + ret = vips_object_set(VIPS_OBJECT(operation), "speed", params->heifEffort, + NULL); + } +#endif + + if (!ret && params->quality) { + ret = vips_object_set(VIPS_OBJECT(operation), "Q", params->quality, NULL); + } + + return ret; +} + +// https://github.com/libvips/libvips/blob/master/libvips/foreign/heifsave.c#L653 +int set_avifsave_options(VipsOperation *operation, SaveParams *params) { + int ret = vips_object_set(VIPS_OBJECT(operation), "strip", params->stripMetadata, "compression", + VIPS_FOREIGN_HEIF_COMPRESSION_AV1, "lossless", + params->heifLossless, NULL); + +#if (VIPS_MAJOR_VERSION >= 8) && (VIPS_MINOR_VERSION >= 13) + if (!ret && params->heifBitdepth && params->heifEffort) { + ret = vips_object_set(VIPS_OBJECT(operation), "bitdepth", + params->heifBitdepth, "effort", params->heifEffort, + NULL); + } +#else + if (!ret && params->heifEffort) { + ret = vips_object_set(VIPS_OBJECT(operation), "speed", params->heifEffort, + NULL); + } +#endif + + if (!ret && params->quality) { + ret = vips_object_set(VIPS_OBJECT(operation), "Q", params->quality, NULL); + } + + return ret; +} + +int set_jp2ksave_options(VipsOperation *operation, SaveParams *params) { + int ret = vips_object_set( + VIPS_OBJECT(operation), "subsample_mode", params->jpegSubsample, + "tile_height", params->jp2kTileHeight, "tile_width", params->jp2kTileWidth, + "lossless", params->jp2kLossless, NULL); + + if (!ret && params->quality) { + ret = vips_object_set(VIPS_OBJECT(operation), "Q", params->quality, NULL); + } + + return ret; +} + +int set_jxlsave_options(VipsOperation *operation, SaveParams *params) { + int ret = vips_object_set( + VIPS_OBJECT(operation), "tier", params->jxlTier, + "distance", params->jxlDistance, "effort", params->jxlEffort, + "lossless", params->jxlLossless, NULL); + + if (!ret && params->quality) { + ret = vips_object_set(VIPS_OBJECT(operation), "Q", params->quality, NULL); + } + + return ret; +} + +int load_from_buffer(LoadParams *params, void *buf, size_t len) { + switch (params->inputFormat) { + case JPEG: + return load_buffer("jpegload_buffer", buf, len, params, + set_jpegload_options); + case PNG: + return load_buffer("pngload_buffer", buf, len, params, + set_pngload_options); + case WEBP: + return load_buffer("webpload_buffer", buf, len, params, + set_webpload_options); + case HEIF: + return load_buffer("heifload_buffer", buf, len, params, + set_heifload_options); + case TIFF: + return load_buffer("tiffload_buffer", buf, len, params, + set_tiffload_options); + case SVG: + return load_buffer("svgload_buffer", buf, len, params, + set_svgload_options); + case GIF: + return load_buffer("gifload_buffer", buf, len, params, + set_gifload_options); + case PDF: + return load_buffer("pdfload_buffer", buf, len, params, + set_pdfload_options); + case MAGICK: + return load_buffer("magickload_buffer", buf, len, params, + set_magickload_options); + case AVIF: + return load_buffer("heifload_buffer", buf, len, params, + set_heifload_options); + case JP2K: + return load_buffer("jp2kload_buffer", buf, len, params, + set_jp2kload_options); + case JXL: + return load_buffer("jxlload_buffer", buf, len, params, + set_jxlload_options); + default: + g_warning("Unsupported input type given: %d", params->inputFormat); + } + return 1; +} + +int save_to_buffer(SaveParams *params) { + switch (params->outputFormat) { + case JPEG: + return save_buffer("jpegsave_buffer", params, set_jpegsave_options); + case PNG: + return save_buffer("pngsave_buffer", params, set_pngsave_options); + case WEBP: + return save_buffer("webpsave_buffer", params, set_webpsave_options); + case HEIF: + return save_buffer("heifsave_buffer", params, set_heifsave_options); + case TIFF: + return save_buffer("tiffsave_buffer", params, set_tiffsave_options); + case GIF: +#if (VIPS_MAJOR_VERSION >= 8) && (VIPS_MINOR_VERSION >= 12) + return save_buffer("gifsave_buffer", params, set_gifsave_options); +#else + return save_buffer("magicksave_buffer", params, set_magicksave_options); +#endif + case AVIF: + return save_buffer("heifsave_buffer", params, set_avifsave_options); + case JP2K: + return save_buffer("jp2ksave_buffer", params, set_jp2ksave_options); + case JXL: + return save_buffer("jxlsave_buffer", params, set_jxlsave_options); + default: + g_warning("Unsupported output type given: %d", params->outputFormat); + } + return 1; +} + +LoadParams create_load_params(ImageType inputFormat) { + Param defaultParam = {}; + LoadParams p = { + .inputFormat = inputFormat, + .inputBlob = NULL, + .outputImage = NULL, + .autorotate = defaultParam, + .fail = defaultParam, + .page = defaultParam, + .n = defaultParam, + .dpi = defaultParam, + .jpegShrink = defaultParam, + .heifThumbnail = defaultParam, + .svgUnlimited = defaultParam, + }; + return p; +} + +// TODO: Change to same pattern as ImportParams + +static SaveParams defaultSaveParams = { + .inputImage = NULL, + .outputBuffer = NULL, + .outputFormat = JPEG, + .outputLen = 0, + + .interlace = FALSE, + .quality = 0, + .stripMetadata = FALSE, + + .jpegOptimizeCoding = FALSE, + .jpegSubsample = VIPS_FOREIGN_JPEG_SUBSAMPLE_ON, + .jpegTrellisQuant = FALSE, + .jpegOvershootDeringing = FALSE, + .jpegOptimizeScans = FALSE, + .jpegQuantTable = 0, + + .pngCompression = 6, + .pngPalette = FALSE, + .pngBitdepth = 0, + .pngDither = 0, + .pngFilter = VIPS_FOREIGN_PNG_FILTER_NONE, + + .gifDither = 0.0, + .gifEffort = 0, + .gifBitdepth = 0, + + .webpLossless = FALSE, + .webpNearLossless = FALSE, + .webpReductionEffort = 4, + .webpIccProfile = NULL, + .webpKMax = 0, + .webpKMin = 0, + .webpMinSize = FALSE, + + .heifBitdepth = 8, + .heifLossless = FALSE, + .heifEffort = 5, + + .tiffCompression = VIPS_FOREIGN_TIFF_COMPRESSION_LZW, + .tiffPredictor = VIPS_FOREIGN_TIFF_PREDICTOR_HORIZONTAL, + .tiffPyramid = FALSE, + .tiffTile = FALSE, + .tiffTileHeight = 256, + .tiffTileWidth = 256, + + .jp2kLossless = FALSE, + .jp2kTileHeight = 512, + .jp2kTileWidth = 512, + + .jxlTier = 0, + .jxlDistance = 1.0, + .jxlEffort = 7, + .jxlLossless = FALSE, + }; + +SaveParams create_save_params(ImageType outputFormat) { + SaveParams params = defaultSaveParams; + params.outputFormat = outputFormat; + return params; +} diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/foreign.go b/vendor/github.com/davidbyttow/govips/v2/vips/foreign.go new file mode 100644 index 00000000000..695617f5307 --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/foreign.go @@ -0,0 +1,502 @@ +package vips + +// #include "foreign.h" +import "C" +import ( + "bytes" + "encoding/xml" + "fmt" + "image/png" + "math" + "runtime" + "unsafe" + + "golang.org/x/image/bmp" + "golang.org/x/net/html/charset" +) + +// SubsampleMode correlates to a libvips subsample mode +type SubsampleMode int + +// SubsampleMode enum correlating to libvips subsample modes +const ( + VipsForeignSubsampleAuto SubsampleMode = C.VIPS_FOREIGN_JPEG_SUBSAMPLE_AUTO + VipsForeignSubsampleOn SubsampleMode = C.VIPS_FOREIGN_JPEG_SUBSAMPLE_ON + VipsForeignSubsampleOff SubsampleMode = C.VIPS_FOREIGN_JPEG_SUBSAMPLE_OFF + VipsForeignSubsampleLast SubsampleMode = C.VIPS_FOREIGN_JPEG_SUBSAMPLE_LAST +) + +// ImageType represents an image type +type ImageType int + +// ImageType enum +const ( + ImageTypeUnknown ImageType = C.UNKNOWN + ImageTypeGIF ImageType = C.GIF + ImageTypeJPEG ImageType = C.JPEG + ImageTypeMagick ImageType = C.MAGICK + ImageTypePDF ImageType = C.PDF + ImageTypePNG ImageType = C.PNG + ImageTypeSVG ImageType = C.SVG + ImageTypeTIFF ImageType = C.TIFF + ImageTypeWEBP ImageType = C.WEBP + ImageTypeHEIF ImageType = C.HEIF + ImageTypeBMP ImageType = C.BMP + ImageTypeAVIF ImageType = C.AVIF + ImageTypeJP2K ImageType = C.JP2K + ImageTypeJXL ImageType = C.JXL +) + +var imageTypeExtensionMap = map[ImageType]string{ + ImageTypeGIF: ".gif", + ImageTypeJPEG: ".jpeg", + ImageTypeMagick: ".magick", + ImageTypePDF: ".pdf", + ImageTypePNG: ".png", + ImageTypeSVG: ".svg", + ImageTypeTIFF: ".tiff", + ImageTypeWEBP: ".webp", + ImageTypeHEIF: ".heic", + ImageTypeBMP: ".bmp", + ImageTypeAVIF: ".avif", + ImageTypeJP2K: ".jp2", + ImageTypeJXL: ".jxl", +} + +// ImageTypes defines the various image types supported by govips +var ImageTypes = map[ImageType]string{ + ImageTypeGIF: "gif", + ImageTypeJPEG: "jpeg", + ImageTypeMagick: "magick", + ImageTypePDF: "pdf", + ImageTypePNG: "png", + ImageTypeSVG: "svg", + ImageTypeTIFF: "tiff", + ImageTypeWEBP: "webp", + ImageTypeHEIF: "heif", + ImageTypeBMP: "bmp", + ImageTypeAVIF: "heif", + ImageTypeJP2K: "jp2k", + ImageTypeJXL: "jxl", +} + +// TiffCompression represents method for compressing a tiff at export +type TiffCompression int + +// TiffCompression enum +const ( + TiffCompressionNone TiffCompression = C.VIPS_FOREIGN_TIFF_COMPRESSION_NONE + TiffCompressionJpeg TiffCompression = C.VIPS_FOREIGN_TIFF_COMPRESSION_JPEG + TiffCompressionDeflate TiffCompression = C.VIPS_FOREIGN_TIFF_COMPRESSION_DEFLATE + TiffCompressionPackbits TiffCompression = C.VIPS_FOREIGN_TIFF_COMPRESSION_PACKBITS + TiffCompressionFax4 TiffCompression = C.VIPS_FOREIGN_TIFF_COMPRESSION_CCITTFAX4 + TiffCompressionLzw TiffCompression = C.VIPS_FOREIGN_TIFF_COMPRESSION_LZW + TiffCompressionWebp TiffCompression = C.VIPS_FOREIGN_TIFF_COMPRESSION_WEBP + TiffCompressionZstd TiffCompression = C.VIPS_FOREIGN_TIFF_COMPRESSION_ZSTD +) + +// TiffPredictor represents method for compressing a tiff at export +type TiffPredictor int + +// TiffPredictor enum +const ( + TiffPredictorNone TiffPredictor = C.VIPS_FOREIGN_TIFF_PREDICTOR_NONE + TiffPredictorHorizontal TiffPredictor = C.VIPS_FOREIGN_TIFF_PREDICTOR_HORIZONTAL + TiffPredictorFloat TiffPredictor = C.VIPS_FOREIGN_TIFF_PREDICTOR_FLOAT +) + +// PngFilter represents filter algorithms that can be applied before compression. +// See https://www.w3.org/TR/PNG-Filters.html +type PngFilter int + +// PngFilter enum +const ( + PngFilterNone PngFilter = C.VIPS_FOREIGN_PNG_FILTER_NONE + PngFilterSub PngFilter = C.VIPS_FOREIGN_PNG_FILTER_SUB + PngFilterUo PngFilter = C.VIPS_FOREIGN_PNG_FILTER_UP + PngFilterAvg PngFilter = C.VIPS_FOREIGN_PNG_FILTER_AVG + PngFilterPaeth PngFilter = C.VIPS_FOREIGN_PNG_FILTER_PAETH + PngFilterAll PngFilter = C.VIPS_FOREIGN_PNG_FILTER_ALL +) + +// FileExt returns the canonical extension for the ImageType +func (i ImageType) FileExt() string { + if ext, ok := imageTypeExtensionMap[i]; ok { + return ext + } + return "" +} + +// IsTypeSupported checks whether given image type is supported by govips +func IsTypeSupported(imageType ImageType) bool { + startupIfNeeded() + + return supportedImageTypes[imageType] +} + +// DetermineImageType attempts to determine the image type of the given buffer +func DetermineImageType(buf []byte) ImageType { + if len(buf) < 12 { + return ImageTypeUnknown + } else if isJPEG(buf) { + return ImageTypeJPEG + } else if isPNG(buf) { + return ImageTypePNG + } else if isGIF(buf) { + return ImageTypeGIF + } else if isTIFF(buf) { + return ImageTypeTIFF + } else if isWEBP(buf) { + return ImageTypeWEBP + } else if isAVIF(buf) { + return ImageTypeAVIF + } else if isHEIF(buf) { + return ImageTypeHEIF + } else if isSVG(buf) { + return ImageTypeSVG + } else if isPDF(buf) { + return ImageTypePDF + } else if isBMP(buf) { + return ImageTypeBMP + } else if isJP2K(buf) { + return ImageTypeJP2K + } else if isJXL(buf) { + return ImageTypeJXL + } else { + return ImageTypeUnknown + } +} + +var jpeg = []byte("\xFF\xD8\xFF") + +func isJPEG(buf []byte) bool { + return bytes.HasPrefix(buf, jpeg) +} + +var gifHeader = []byte("\x47\x49\x46") + +func isGIF(buf []byte) bool { + return bytes.HasPrefix(buf, gifHeader) +} + +var pngHeader = []byte("\x89\x50\x4E\x47") + +func isPNG(buf []byte) bool { + return bytes.HasPrefix(buf, pngHeader) +} + +var tifII = []byte("\x49\x49\x2A\x00") +var tifMM = []byte("\x4D\x4D\x00\x2A") + +func isTIFF(buf []byte) bool { + return bytes.HasPrefix(buf, tifII) || bytes.HasPrefix(buf, tifMM) +} + +var webpHeader = []byte("\x57\x45\x42\x50") + +func isWEBP(buf []byte) bool { + return bytes.Equal(buf[8:12], webpHeader) +} + +// https://github.com/strukturag/libheif/blob/master/libheif/heif.cc +var ftyp = []byte("ftyp") +var heic = []byte("heic") +var mif1 = []byte("mif1") +var msf1 = []byte("msf1") +var avif = []byte("avif") + +func isHEIF(buf []byte) bool { + return bytes.Equal(buf[4:8], ftyp) && (bytes.Equal(buf[8:12], heic) || + bytes.Equal(buf[8:12], mif1) || + bytes.Equal(buf[8:12], msf1)) || + isAVIF(buf) +} + +func isAVIF(buf []byte) bool { + return bytes.Equal(buf[4:8], ftyp) && bytes.Equal(buf[8:12], avif) +} + +var svg = []byte(" + +#include +#include +// clang-format n + +#ifndef BOOL +#define BOOL int +#endif + +typedef enum types { + UNKNOWN = 0, + JPEG, + WEBP, + PNG, + TIFF, + GIF, + PDF, + SVG, + MAGICK, + HEIF, + BMP, + AVIF, + JP2K, + JXL +} ImageType; + +typedef enum ParamType { + PARAM_TYPE_NULL, + PARAM_TYPE_BOOL, + PARAM_TYPE_INT, + PARAM_TYPE_DOUBLE, +} ParamType; + +typedef struct Param { + ParamType type; + + union Value { + gboolean b; + gint i; + gdouble d; + } value; + + gboolean is_set; + +} Param; + +void set_bool_param(Param *p, gboolean b); +void set_int_param(Param *p, gint i); +void set_double_param(Param *p, gdouble d); + +typedef struct LoadParams { + ImageType inputFormat; + VipsBlob *inputBlob; + VipsImage *outputImage; + + Param autorotate; + Param fail; + Param page; + Param n; + Param dpi; + Param jpegShrink; + Param heifThumbnail; + Param svgUnlimited; + +} LoadParams; + +LoadParams create_load_params(ImageType inputFormat); +int load_from_buffer(LoadParams *params, void *buf, size_t len); + +typedef struct SaveParams { + VipsImage *inputImage; + void *outputBuffer; + ImageType outputFormat; + size_t outputLen; + + BOOL stripMetadata; + int quality; + BOOL interlace; + + // JPEG + BOOL jpegOptimizeCoding; + VipsForeignJpegSubsample jpegSubsample; + BOOL jpegTrellisQuant; + BOOL jpegOvershootDeringing; + BOOL jpegOptimizeScans; + int jpegQuantTable; + + // PNG + int pngCompression; + VipsForeignPngFilter pngFilter; + BOOL pngPalette; + double pngDither; + int pngBitdepth; + + // GIF (with CGIF) + double gifDither; + int gifEffort; + int gifBitdepth; + + // WEBP + BOOL webpLossless; + BOOL webpNearLossless; + int webpReductionEffort; + char *webpIccProfile; + BOOL webpMinSize; + int webpKMin; + int webpKMax; + + // HEIF - https://github.com/libvips/libvips/blob/master/libvips/foreign/heifsave.c#L71 + int heifBitdepth; // Bitdepth to save at for >8 bit images + BOOL heifLossless; // Lossless compression + int heifEffort; // CPU effort (0 - 9) + + // TIFF + VipsForeignTiffCompression tiffCompression; + VipsForeignTiffPredictor tiffPredictor; + BOOL tiffPyramid; + BOOL tiffTile; + int tiffTileHeight; + int tiffTileWidth; + + // JPEG2000 + BOOL jp2kLossless; + int jp2kTileWidth; + int jp2kTileHeight; + + // JXL + int jxlTier; + double jxlDistance; + int jxlEffort; + BOOL jxlLossless; +} SaveParams; + +SaveParams create_save_params(ImageType outputFormat); +int save_to_buffer(SaveParams *params); + diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/govips.c b/vendor/github.com/davidbyttow/govips/v2/vips/govips.c new file mode 100644 index 00000000000..0b220b9ece7 --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/govips.c @@ -0,0 +1,27 @@ +#include "govips.h" + +static void govips_logging_handler(const gchar *log_domain, + GLogLevelFlags log_level, + const gchar *message, gpointer user_data) { + govipsLoggingHandler((char *)log_domain, (int)log_level, (char *)message); +} + +static void null_logging_handler(const gchar *log_domain, + GLogLevelFlags log_level, const gchar *message, + gpointer user_data) {} + +void vips_set_logging_handler(void) { + g_log_set_default_handler(govips_logging_handler, NULL); +} + +void vips_unset_logging_handler(void) { + g_log_set_default_handler(null_logging_handler, NULL); +} + +/* This function skips the Govips logging handler and logs + directly to stdout. To be used only for testing and debugging. + Needed for CI because of a Go macOS bug which doesn't clean cgo callbacks on + exit. */ +void vips_default_logging_handler(void) { + g_log_set_default_handler(g_log_default_handler, NULL); +} \ No newline at end of file diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/govips.go b/vendor/github.com/davidbyttow/govips/v2/vips/govips.go new file mode 100644 index 00000000000..6969b62abf4 --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/govips.go @@ -0,0 +1,265 @@ +// Package vips provides go bindings for libvips, a fast image processing library. +package vips + +// #cgo pkg-config: vips +// #include +// #include "govips.h" +import "C" +import ( + "fmt" + "os" + "runtime" + "strings" + "sync" +) + +const ( + defaultConcurrencyLevel = 1 + defaultMaxCacheMem = 50 * 1024 * 1024 + defaultMaxCacheSize = 100 + defaultMaxCacheFiles = 0 +) + +var ( + // Version is the full libvips version string (x.y.z) + Version = C.GoString(C.vips_version_string()) + + // MajorVersion is the libvips major component of the version string (x in x.y.z) + MajorVersion = int(C.vips_version(0)) + + // MinorVersion is the libvips minor component of the version string (y in x.y.z) + MinorVersion = int(C.vips_version(1)) + + // MicroVersion is the libvips micro component of the version string (z in x.y.z) + // Also known as patch version + MicroVersion = int(C.vips_version(2)) + + running = false + hasShutdown = false + initLock sync.Mutex + statCollectorDone chan struct{} + once sync.Once + typeLoaders = make(map[string]ImageType) + supportedImageTypes = make(map[ImageType]bool) +) + +// Config allows fine-tuning of libvips library +type Config struct { + ConcurrencyLevel int + MaxCacheFiles int + MaxCacheMem int + MaxCacheSize int + ReportLeaks bool + CacheTrace bool + CollectStats bool +} + +// Startup sets up the libvips support and ensures the versions are correct. Pass in nil for +// default configuration. +func Startup(config *Config) { + if hasShutdown { + panic("govips cannot be stopped and restarted") + } + + initLock.Lock() + defer initLock.Unlock() + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if running { + govipsLog("govips", LogLevelInfo, "warning libvips already started") + return + } + + if MajorVersion < 8 { + panic("govips requires libvips version 8.10+") + } + + if MajorVersion == 8 && MinorVersion < 10 { + panic("govips requires libvips version 8.10+") + } + + cName := C.CString("govips") + defer freeCString(cName) + + // Initialize govips logging handler and verbosity filter to historical default + if !currentLoggingOverridden { + govipsLoggingSettings(nil, LogLevelInfo) + } + + // Override default glib logging handler to intercept logging messages + enableLogging() + + err := C.vips_init(cName) + if err != 0 { + panic(fmt.Sprintf("Failed to start vips code=%v", err)) + } + + initializeICCProfiles() + + running = true + + if config != nil { + if config.CollectStats { + statCollectorDone = collectStats() + } + + C.vips_leak_set(toGboolean(config.ReportLeaks)) + + if config.ConcurrencyLevel >= 0 { + C.vips_concurrency_set(C.int(config.ConcurrencyLevel)) + } else { + C.vips_concurrency_set(defaultConcurrencyLevel) + } + + if config.MaxCacheFiles >= 0 { + C.vips_cache_set_max_files(C.int(config.MaxCacheFiles)) + } else { + C.vips_cache_set_max_files(defaultMaxCacheFiles) + } + + if config.MaxCacheMem >= 0 { + C.vips_cache_set_max_mem(C.size_t(config.MaxCacheMem)) + } else { + C.vips_cache_set_max_mem(defaultMaxCacheMem) + } + + if config.MaxCacheSize >= 0 { + C.vips_cache_set_max(C.int(config.MaxCacheSize)) + } else { + C.vips_cache_set_max(defaultMaxCacheSize) + } + + if config.CacheTrace { + C.vips_cache_set_trace(toGboolean(true)) + } + } else { + C.vips_concurrency_set(defaultConcurrencyLevel) + C.vips_cache_set_max(defaultMaxCacheSize) + C.vips_cache_set_max_mem(defaultMaxCacheMem) + C.vips_cache_set_max_files(defaultMaxCacheFiles) + } + + govipsLog("govips", LogLevelInfo, fmt.Sprintf("vips %s started with concurrency=%d cache_max_files=%d cache_max_mem=%d cache_max=%d", + Version, + int(C.vips_concurrency_get()), + int(C.vips_cache_get_max_files()), + int(C.vips_cache_get_max_mem()), + int(C.vips_cache_get_max()))) + + initTypes() +} + +func enableLogging() { + C.vips_set_logging_handler() +} + +func disableLogging() { + C.vips_unset_logging_handler() +} + +// consoleLogging overrides the Govips logging handler and makes glib +// use its default logging handler which outputs everything to console. +// Needed for CI unit testing due to a macOS bug in Go (doesn't clean cgo callbacks on exit) +func consoleLogging() { + C.vips_default_logging_handler() +} + +// Shutdown libvips +func Shutdown() { + hasShutdown = true + + if statCollectorDone != nil { + statCollectorDone <- struct{}{} + } + + initLock.Lock() + defer initLock.Unlock() + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if !running { + govipsLog("govips", LogLevelInfo, "warning libvips not started") + return + } + + os.RemoveAll(temporaryDirectory) + + C.vips_shutdown() + disableLogging() + running = false +} + +// ShutdownThread clears the cache for for the given thread. This needs to be +// called when a thread using vips exits. +func ShutdownThread() { + C.vips_thread_shutdown() +} + +// ClearCache drops the whole operation cache, handy for leak tracking. +func ClearCache() { + C.vips_cache_drop_all() +} + +// PrintCache prints the whole operation cache to stdout for debugging purposes. +func PrintCache() { + C.vips_cache_print() +} + +// PrintObjectReport outputs all of the current internal objects in libvips +func PrintObjectReport(label string) { + govipsLog("govips", LogLevelInfo, fmt.Sprintf("\n=======================================\nvips live objects: %s...\n", label)) + C.vips_object_print_all() + govipsLog("govips", LogLevelInfo, "=======================================\n\n") +} + +// MemoryStats is a data structure that houses various memory statistics from ReadVipsMemStats() +type MemoryStats struct { + Mem int64 + MemHigh int64 + Files int64 + Allocs int64 +} + +// ReadVipsMemStats returns various memory statistics such as allocated memory and open files. +func ReadVipsMemStats(stats *MemoryStats) { + stats.Mem = int64(C.vips_tracked_get_mem()) + stats.MemHigh = int64(C.vips_tracked_get_mem_highwater()) + stats.Allocs = int64(C.vips_tracked_get_allocs()) + stats.Files = int64(C.vips_tracked_get_files()) +} + +func startupIfNeeded() { + if !running { + govipsLog("govips", LogLevelInfo, "libvips was forcibly started automatically, consider calling Startup/Shutdown yourself") + Startup(nil) + } +} + +// InitTypes initializes caches and figures out which image types are supported +func initTypes() { + once.Do(func() { + cType := C.CString("VipsOperation") + defer freeCString(cType) + + for k, v := range ImageTypes { + name := strings.ToLower("VipsForeignLoad" + v) + typeLoaders[name] = k + typeLoaders[name+"buffer"] = k + + cFunc := C.CString(v + "load") + //noinspection GoDeferInLoop + defer freeCString(cFunc) + + ret := C.vips_type_find(cType, cFunc) + + supportedImageTypes[k] = int(ret) != 0 + + if supportedImageTypes[k] { + govipsLog("govips", LogLevelInfo, fmt.Sprintf("registered image type loader type=%s", v)) + } + } + }) +} diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/govips.h b/vendor/github.com/davidbyttow/govips/v2/vips/govips.h new file mode 100644 index 00000000000..31f42b04c65 --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/govips.h @@ -0,0 +1,26 @@ + +// clang-format off +// include order matters +#include +#include +#include +// clang-format on + +#if (VIPS_MAJOR_VERSION < 8) +error_requires_version_8 +#endif + + extern void + govipsLoggingHandler(char *log_domain, int log_level, char *message); + +static void govips_logging_handler(const gchar *log_domain, + GLogLevelFlags log_level, + const gchar *message, gpointer user_data); + +static void null_logging_handler(const gchar *log_domain, + GLogLevelFlags log_level, const gchar *message, + gpointer user_data); + +void vips_set_logging_handler(void); +void vips_unset_logging_handler(void); +void vips_default_logging_handler(void); \ No newline at end of file diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/header.c b/vendor/github.com/davidbyttow/govips/v2/vips/header.c new file mode 100644 index 00000000000..6744c840d70 --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/header.c @@ -0,0 +1,122 @@ +#include "header.h" + +#include + +unsigned long has_icc_profile(VipsImage *in) { + return vips_image_get_typeof(in, VIPS_META_ICC_NAME); +} + +unsigned long get_icc_profile(VipsImage *in, const void **data, + size_t *dataLength) { + return image_get_blob(in, VIPS_META_ICC_NAME, data, dataLength); +} + +gboolean remove_icc_profile(VipsImage *in) { + return vips_image_remove(in, VIPS_META_ICC_NAME); +} + +unsigned long has_iptc(VipsImage *in) { + return vips_image_get_typeof(in, VIPS_META_IPTC_NAME); +} + +char **image_get_fields(VipsImage *in) { return vips_image_get_fields(in); } + +void image_set_string(VipsImage *in, const char *name, const char *str) { + vips_image_set_string(in, name, str); +} + +unsigned long image_get_string(VipsImage *in, const char *name, + const char **out) { + return vips_image_get_string(in, name, out); +} + +unsigned long image_get_as_string(VipsImage *in, const char *name, char **out) { + return vips_image_get_as_string(in, name, out); +} + +void remove_field(VipsImage *in, char *field) { vips_image_remove(in, field); } + +int get_meta_orientation(VipsImage *in) { + int orientation = 0; + if (vips_image_get_typeof(in, VIPS_META_ORIENTATION) != 0) { + vips_image_get_int(in, VIPS_META_ORIENTATION, &orientation); + } + + return orientation; +} + +void remove_meta_orientation(VipsImage *in) { + vips_image_remove(in, VIPS_META_ORIENTATION); +} + +void set_meta_orientation(VipsImage *in, int orientation) { + vips_image_set_int(in, VIPS_META_ORIENTATION, orientation); +} + +// https://libvips.github.io/libvips/API/current/libvips-header.html#vips-image-get-n-pages +int get_image_n_pages(VipsImage *in) { + int n_pages = 0; + n_pages = vips_image_get_n_pages(in); + return n_pages; +} + +void set_image_n_pages(VipsImage *in, int n_pages) { + vips_image_set_int(in, VIPS_META_N_PAGES, n_pages); +} + +// https://www.libvips.org/API/current/libvips-header.html#vips-image-get-page-height +int get_page_height(VipsImage *in) { + int page_height = 0; + page_height = vips_image_get_page_height(in); + return page_height; +} + +void set_page_height(VipsImage *in, int height) { + vips_image_set_int(in, VIPS_META_PAGE_HEIGHT, height); +} + +int get_meta_loader(const VipsImage *in, const char **out) { + return vips_image_get_string(in, VIPS_META_LOADER, out); +} + +int get_image_delay(VipsImage *in, int **out) { + return vips_image_get_array_int(in, "delay", out, NULL); +} + +void set_image_delay(VipsImage *in, const int *array, int n) { + return vips_image_set_array_int(in, "delay", array, n); +} + +void image_set_double(VipsImage *in, const char *name, double i) { + vips_image_set_double(in, name, i); +} + +unsigned long image_get_double(VipsImage *in, const char *name, double *out) { + return vips_image_get_double(in, name, out); +} + +void image_set_int(VipsImage *in, const char *name, int i) { + vips_image_set_int(in, name, i); +} + +unsigned long image_get_int(VipsImage *in, const char *name, int *out) { + return vips_image_get_int(in, name, out); +} + +void image_set_blob(VipsImage *in, const char *name, const void *data, + size_t dataLength) { + vips_image_set_blob_copy(in, name, data, dataLength); +} + +unsigned long image_get_blob(VipsImage *in, const char *name, const void **data, + size_t *dataLength) { + if (vips_image_get_typeof(in, name) == 0) { + return 0; + } + + if (vips_image_get_blob(in, name, data, dataLength)) { + return -1; + } + + return 0; +} \ No newline at end of file diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/header.go b/vendor/github.com/davidbyttow/govips/v2/vips/header.go new file mode 100644 index 00000000000..a85e47c5166 --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/header.go @@ -0,0 +1,289 @@ +package vips + +// #include "header.h" +import "C" +import ( + "strings" + "unsafe" +) + +func vipsHasICCProfile(in *C.VipsImage) bool { + return int(C.has_icc_profile(in)) != 0 +} + +func vipsGetICCProfile(in *C.VipsImage) ([]byte, bool) { + var bufPtr unsafe.Pointer + var dataLength C.size_t + + if int(C.get_icc_profile(in, &bufPtr, &dataLength)) != 0 { + return nil, false + } + + buf := C.GoBytes(bufPtr, C.int(dataLength)) + return buf, true +} + +func vipsRemoveICCProfile(in *C.VipsImage) bool { + return fromGboolean(C.remove_icc_profile(in)) +} + +func vipsHasIPTC(in *C.VipsImage) bool { + return int(C.has_iptc(in)) != 0 +} + +func vipsImageGetFields(in *C.VipsImage) (fields []string) { + const maxFields = 256 + + rawFields := C.image_get_fields(in) + defer C.g_strfreev(rawFields) + + cFields := (*[maxFields]*C.char)(unsafe.Pointer(rawFields))[:maxFields:maxFields] + + for _, field := range cFields { + if field == nil { + break + } + fields = append(fields, C.GoString(field)) + } + return +} + +func vipsImageGetExifData(in *C.VipsImage) map[string]string { + fields := vipsImageGetFields(in) + + exifData := map[string]string{} + for _, field := range fields { + if strings.HasPrefix(field, "exif") { + exifData[field] = vipsImageGetString(in, field) + } + } + + return exifData +} + +func vipsRemoveMetadata(in *C.VipsImage, keep ...string) { + fields := vipsImageGetFields(in) + + retain := append(keep, technicalMetadata...) + + for _, field := range fields { + if contains(retain, field) { + continue + } + + cField := C.CString(field) + + C.remove_field(in, cField) + + C.free(unsafe.Pointer(cField)) + } +} + +var technicalMetadata = []string{ + C.VIPS_META_ICC_NAME, + C.VIPS_META_ORIENTATION, + C.VIPS_META_N_PAGES, + C.VIPS_META_PAGE_HEIGHT, +} + +func contains(a []string, x string) bool { + for _, n := range a { + if x == n { + return true + } + } + return false +} + +func vipsGetMetaOrientation(in *C.VipsImage) int { + return int(C.get_meta_orientation(in)) +} + +func vipsRemoveMetaOrientation(in *C.VipsImage) { + C.remove_meta_orientation(in) +} + +func vipsSetMetaOrientation(in *C.VipsImage, orientation int) { + C.set_meta_orientation(in, C.int(orientation)) +} + +func vipsGetImageNPages(in *C.VipsImage) int { + return int(C.get_image_n_pages(in)) +} + +func vipsSetImageNPages(in *C.VipsImage, pages int) { + C.set_image_n_pages(in, C.int(pages)) +} + +func vipsGetPageHeight(in *C.VipsImage) int { + return int(C.get_page_height(in)) +} + +func vipsSetPageHeight(in *C.VipsImage, height int) { + C.set_page_height(in, C.int(height)) +} + +func vipsImageGetMetaLoader(in *C.VipsImage) (string, bool) { + var out *C.char + defer freeCString(out) + code := int(C.get_meta_loader(in, &out)) + return C.GoString(out), code == 0 +} + +func vipsImageGetDelay(in *C.VipsImage, n int) ([]int, error) { + incOpCounter("imageGetDelay") + var out *C.int + defer gFreePointer(unsafe.Pointer(out)) + + if err := C.get_image_delay(in, &out); err != 0 { + return nil, handleVipsError() + } + return fromCArrayInt(out, n), nil +} + +func vipsImageSetDelay(in *C.VipsImage, data []C.int) error { + incOpCounter("imageSetDelay") + if n := len(data); n > 0 { + C.set_image_delay(in, &data[0], C.int(n)) + } + return nil +} + +// vipsDetermineImageTypeFromMetaLoader determine the image type from vips-loader metadata +func vipsDetermineImageTypeFromMetaLoader(in *C.VipsImage) ImageType { + vipsLoader, ok := vipsImageGetMetaLoader(in) + if vipsLoader == "" || !ok { + return ImageTypeUnknown + } + if strings.HasPrefix(vipsLoader, "jpeg") { + return ImageTypeJPEG + } + if strings.HasPrefix(vipsLoader, "png") { + return ImageTypePNG + } + if strings.HasPrefix(vipsLoader, "gif") { + return ImageTypeGIF + } + if strings.HasPrefix(vipsLoader, "svg") { + return ImageTypeSVG + } + if strings.HasPrefix(vipsLoader, "webp") { + return ImageTypeWEBP + } + if strings.HasPrefix(vipsLoader, "jp2k") { + return ImageTypeJP2K + } + if strings.HasPrefix(vipsLoader, "jxl") { + return ImageTypeJXL + } + if strings.HasPrefix(vipsLoader, "magick") { + return ImageTypeMagick + } + if strings.HasPrefix(vipsLoader, "tiff") { + return ImageTypeTIFF + } + if strings.HasPrefix(vipsLoader, "heif") { + return ImageTypeHEIF + } + if strings.HasPrefix(vipsLoader, "pdf") { + return ImageTypePDF + } + return ImageTypeUnknown +} + +func vipsImageSetBlob(in *C.VipsImage, name string, data []byte) { + cData := unsafe.Pointer(&data) + cDataLength := C.size_t(len(data)) + + cField := C.CString(name) + defer freeCString(cField) + C.image_set_blob(in, cField, cData, cDataLength) +} + +func vipsImageGetBlob(in *C.VipsImage, name string) []byte { + var bufPtr unsafe.Pointer + var dataLength C.size_t + + cField := C.CString(name) + defer freeCString(cField) + if int(C.image_get_blob(in, cField, &bufPtr, &dataLength)) != 0 { + return nil + } + + buf := C.GoBytes(bufPtr, C.int(dataLength)) + return buf +} + +func vipsImageSetDouble(in *C.VipsImage, name string, f float64) { + cField := C.CString(name) + defer freeCString(cField) + + cDouble := C.double(f) + C.image_set_double(in, cField, cDouble) +} + +func vipsImageGetDouble(in *C.VipsImage, name string) float64 { + cField := C.CString(name) + defer freeCString(cField) + + var cDouble C.double + if int(C.image_get_double(in, cField, &cDouble)) == 0 { + return float64(cDouble) + } + + return 0 +} + +func vipsImageSetInt(in *C.VipsImage, name string, i int) { + cField := C.CString(name) + defer freeCString(cField) + + cInt := C.int(i) + C.image_set_int(in, cField, cInt) +} + +func vipsImageGetInt(in *C.VipsImage, name string) int { + cField := C.CString(name) + defer freeCString(cField) + + var cInt C.int + if int(C.image_get_int(in, cField, &cInt)) == 0 { + return int(cInt) + } + + return 0 +} + +func vipsImageSetString(in *C.VipsImage, name string, str string) { + cField := C.CString(name) + defer freeCString(cField) + + cStr := C.CString(str) + defer freeCString(cStr) + + C.image_set_string(in, cField, cStr) +} + +func vipsImageGetString(in *C.VipsImage, name string) string { + cField := C.CString(name) + defer freeCString(cField) + var cFieldValue *C.char + defer freeCString(cFieldValue) + if int(C.image_get_string(in, cField, &cFieldValue)) == 0 { + return C.GoString(cFieldValue) + } + + return "" +} + +func vipsImageGetAsString(in *C.VipsImage, name string) string { + cField := C.CString(name) + defer freeCString(cField) + var cFieldValue *C.char + defer freeCString(cFieldValue) + if int(C.image_get_as_string(in, cField, &cFieldValue)) == 0 { + return C.GoString(cFieldValue) + } + + return "" +} diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/header.h b/vendor/github.com/davidbyttow/govips/v2/vips/header.h new file mode 100644 index 00000000000..9a4983e2e43 --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/header.h @@ -0,0 +1,41 @@ +// https://libvips.github.io/libvips/API/current/libvips-header.html + +#include +#include + +unsigned long has_icc_profile(VipsImage *in); +unsigned long get_icc_profile(VipsImage *in, const void **data, + size_t *dataLength); +int remove_icc_profile(VipsImage *in); + +unsigned long has_iptc(VipsImage *in); +char **image_get_fields(VipsImage *in); + +void image_set_string(VipsImage *in, const char *name, const char *str); +unsigned long image_get_string(VipsImage *in, const char *name, + const char **out); +unsigned long image_get_as_string(VipsImage *in, const char *name, char **out); + +void remove_field(VipsImage *in, char *field); + +int get_meta_orientation(VipsImage *in); +void remove_meta_orientation(VipsImage *in); +void set_meta_orientation(VipsImage *in, int orientation); +int get_image_n_pages(VipsImage *in); +void set_image_n_pages(VipsImage *in, int n_pages); +int get_page_height(VipsImage *in); +void set_page_height(VipsImage *in, int height); +int get_meta_loader(const VipsImage *in, const char **out); +int get_image_delay(VipsImage *in, int **out); +void set_image_delay(VipsImage *in, const int *array, int n); + +void image_set_blob(VipsImage *in, const char *name, const void *data, + size_t dataLength); +unsigned long image_get_blob(VipsImage *in, const char *name, const void **data, + size_t *dataLength); + +void image_set_double(VipsImage *in, const char *name, double i); +unsigned long image_get_double(VipsImage *in, const char *name, double *out); + +void image_set_int(VipsImage *in, const char *name, int i); +unsigned long image_get_int(VipsImage *in, const char *name, int *out); \ No newline at end of file diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/icc_profiles.go b/vendor/github.com/davidbyttow/govips/v2/vips/icc_profiles.go new file mode 100644 index 00000000000..f10c42923f1 --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/icc_profiles.go @@ -0,0 +1,675 @@ +package vips + +import ( + "fmt" + "os" + "path/filepath" +) + +var ( + // ATTRIBUTION: + // The following micro icc profile taken from: https://github.com/saucecontrol/Compact-ICC-Profiles. + // Read more (very interesting): https://photosauce.net/blog/post/making-a-minimal-srgb-icc-profile-part-1-trim-the-fat-abuse-the-spec + sRGBV2MicroICCProfile = []byte{ + 0x00, 0x00, 0x01, 0xc8, 0x6c, 0x63, 0x6d, 0x73, 0x02, 0x10, 0x00, 0x00, + 0x6d, 0x6e, 0x74, 0x72, 0x52, 0x47, 0x42, 0x20, 0x58, 0x59, 0x5a, 0x20, + 0x07, 0xe2, 0x00, 0x03, 0x00, 0x14, 0x00, 0x09, 0x00, 0x0e, 0x00, 0x1d, + 0x61, 0x63, 0x73, 0x70, 0x4d, 0x53, 0x46, 0x54, 0x00, 0x00, 0x00, 0x00, + 0x73, 0x61, 0x77, 0x73, 0x63, 0x74, 0x72, 0x6c, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf6, 0xd6, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xd3, 0x2d, 0x68, 0x61, 0x6e, 0x64, + 0x9d, 0x91, 0x00, 0x3d, 0x40, 0x80, 0xb0, 0x3d, 0x40, 0x74, 0x2c, 0x81, + 0x9e, 0xa5, 0x22, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, + 0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x5f, + 0x63, 0x70, 0x72, 0x74, 0x00, 0x00, 0x01, 0x0c, 0x00, 0x00, 0x00, 0x0c, + 0x77, 0x74, 0x70, 0x74, 0x00, 0x00, 0x01, 0x18, 0x00, 0x00, 0x00, 0x14, + 0x72, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x01, 0x2c, 0x00, 0x00, 0x00, 0x14, + 0x67, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x14, + 0x62, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x01, 0x54, 0x00, 0x00, 0x00, 0x14, + 0x72, 0x54, 0x52, 0x43, 0x00, 0x00, 0x01, 0x68, 0x00, 0x00, 0x00, 0x60, + 0x67, 0x54, 0x52, 0x43, 0x00, 0x00, 0x01, 0x68, 0x00, 0x00, 0x00, 0x60, + 0x62, 0x54, 0x52, 0x43, 0x00, 0x00, 0x01, 0x68, 0x00, 0x00, 0x00, 0x60, + 0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, + 0x75, 0x52, 0x47, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x74, 0x65, 0x78, 0x74, 0x00, 0x00, 0x00, 0x00, + 0x43, 0x43, 0x30, 0x00, 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf3, 0x54, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x16, 0xc9, + 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6f, 0xa0, + 0x00, 0x00, 0x38, 0xf2, 0x00, 0x00, 0x03, 0x8f, 0x58, 0x59, 0x5a, 0x20, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x96, 0x00, 0x00, 0xb7, 0x89, + 0x00, 0x00, 0x18, 0xda, 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x24, 0xa0, 0x00, 0x00, 0x0f, 0x85, 0x00, 0x00, 0xb6, 0xc4, + 0x63, 0x75, 0x72, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a, + 0x00, 0x00, 0x00, 0x7c, 0x00, 0xf8, 0x01, 0x9c, 0x02, 0x75, 0x03, 0x83, + 0x04, 0xc9, 0x06, 0x4e, 0x08, 0x12, 0x0a, 0x18, 0x0c, 0x62, 0x0e, 0xf4, + 0x11, 0xcf, 0x14, 0xf6, 0x18, 0x6a, 0x1c, 0x2e, 0x20, 0x43, 0x24, 0xac, + 0x29, 0x6a, 0x2e, 0x7e, 0x33, 0xeb, 0x39, 0xb3, 0x3f, 0xd6, 0x46, 0x57, + 0x4d, 0x36, 0x54, 0x76, 0x5c, 0x17, 0x64, 0x1d, 0x6c, 0x86, 0x75, 0x56, + 0x7e, 0x8d, 0x88, 0x2c, 0x92, 0x36, 0x9c, 0xab, 0xa7, 0x8c, 0xb2, 0xdb, + 0xbe, 0x99, 0xca, 0xc7, 0xd7, 0x65, 0xe4, 0x77, 0xf1, 0xf9, 0xff, 0xff, + } + + // ATTRIBUTION: + // The following micro icc profile taken from: https://github.com/saucecontrol/Compact-ICC-Profiles. + // Read more (very interesting): https://photosauce.net/blog/post/making-a-minimal-srgb-icc-profile-part-1-trim-the-fat-abuse-the-spec + sGrayV2MicroICCProfile = []byte{ + 0x00, 0x00, 0x01, 0x50, 0x6c, 0x63, 0x6d, 0x73, 0x02, 0x10, 0x00, 0x00, + 0x6d, 0x6e, 0x74, 0x72, 0x47, 0x52, 0x41, 0x59, 0x58, 0x59, 0x5a, 0x20, + 0x07, 0xe2, 0x00, 0x03, 0x00, 0x14, 0x00, 0x09, 0x00, 0x0e, 0x00, 0x1d, + 0x61, 0x63, 0x73, 0x70, 0x4d, 0x53, 0x46, 0x54, 0x00, 0x00, 0x00, 0x00, + 0x73, 0x61, 0x77, 0x73, 0x63, 0x74, 0x72, 0x6c, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf6, 0xd6, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xd3, 0x2d, 0x68, 0x61, 0x6e, 0x64, + 0x05, 0xd2, 0x02, 0xa7, 0xf9, 0xdd, 0x47, 0x94, 0xc7, 0x4f, 0x4c, 0x5f, + 0x26, 0x82, 0x3a, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, + 0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x00, 0xb4, 0x00, 0x00, 0x00, 0x5f, + 0x63, 0x70, 0x72, 0x74, 0x00, 0x00, 0x00, 0xd0, 0x00, 0x00, 0x00, 0x0c, + 0x77, 0x74, 0x70, 0x74, 0x00, 0x00, 0x00, 0xdc, 0x00, 0x00, 0x00, 0x14, + 0x6b, 0x54, 0x52, 0x43, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x60, + 0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, + 0x75, 0x47, 0x72, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x74, 0x65, 0x78, 0x74, 0x00, 0x00, 0x00, 0x00, + 0x43, 0x43, 0x30, 0x00, 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf3, 0x54, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x16, 0xc9, + 0x63, 0x75, 0x72, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a, + 0x00, 0x00, 0x00, 0x7c, 0x00, 0xf8, 0x01, 0x9c, 0x02, 0x75, 0x03, 0x83, + 0x04, 0xc9, 0x06, 0x4e, 0x08, 0x12, 0x0a, 0x18, 0x0c, 0x62, 0x0e, 0xf4, + 0x11, 0xcf, 0x14, 0xf6, 0x18, 0x6a, 0x1c, 0x2e, 0x20, 0x43, 0x24, 0xac, + 0x29, 0x6a, 0x2e, 0x7e, 0x33, 0xeb, 0x39, 0xb3, 0x3f, 0xd6, 0x46, 0x57, + 0x4d, 0x36, 0x54, 0x76, 0x5c, 0x17, 0x64, 0x1d, 0x6c, 0x86, 0x75, 0x56, + 0x7e, 0x8d, 0x88, 0x2c, 0x92, 0x36, 0x9c, 0xab, 0xa7, 0x8c, 0xb2, 0xdb, + 0xbe, 0x99, 0xca, 0xc7, 0xd7, 0x65, 0xe4, 0x77, 0xf1, 0xf9, 0xff, 0xff, + } + + sRGBIEC6196621ICCProfile = []byte{ + 0x00, 0x00, 0x0b, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x6d, 0x6e, 0x74, 0x72, 0x52, 0x47, 0x42, 0x20, 0x58, 0x59, 0x5a, 0x20, + 0x07, 0xd9, 0x00, 0x03, 0x00, 0x1b, 0x00, 0x15, 0x00, 0x24, 0x00, 0x1f, + 0x61, 0x63, 0x73, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf6, 0xd6, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xd3, 0x2d, 0x00, 0x00, 0x00, 0x00, + 0x29, 0xf8, 0x3d, 0xde, 0xaf, 0xf2, 0x55, 0xae, 0x78, 0x42, 0xfa, 0xe4, + 0xca, 0x83, 0x39, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x01, 0x44, 0x00, 0x00, 0x00, 0x79, + 0x62, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x01, 0xc0, 0x00, 0x00, 0x00, 0x14, + 0x62, 0x54, 0x52, 0x43, 0x00, 0x00, 0x01, 0xd4, 0x00, 0x00, 0x08, 0x0c, + 0x64, 0x6d, 0x64, 0x64, 0x00, 0x00, 0x09, 0xe0, 0x00, 0x00, 0x00, 0x88, + 0x67, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x0a, 0x68, 0x00, 0x00, 0x00, 0x14, + 0x67, 0x54, 0x52, 0x43, 0x00, 0x00, 0x01, 0xd4, 0x00, 0x00, 0x08, 0x0c, + 0x6c, 0x75, 0x6d, 0x69, 0x00, 0x00, 0x0a, 0x7c, 0x00, 0x00, 0x00, 0x14, + 0x6d, 0x65, 0x61, 0x73, 0x00, 0x00, 0x0a, 0x90, 0x00, 0x00, 0x00, 0x24, + 0x62, 0x6b, 0x70, 0x74, 0x00, 0x00, 0x0a, 0xb4, 0x00, 0x00, 0x00, 0x14, + 0x72, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x0a, 0xc8, 0x00, 0x00, 0x00, 0x14, + 0x72, 0x54, 0x52, 0x43, 0x00, 0x00, 0x01, 0xd4, 0x00, 0x00, 0x08, 0x0c, + 0x74, 0x65, 0x63, 0x68, 0x00, 0x00, 0x0a, 0xdc, 0x00, 0x00, 0x00, 0x0c, + 0x76, 0x75, 0x65, 0x64, 0x00, 0x00, 0x0a, 0xe8, 0x00, 0x00, 0x00, 0x87, + 0x77, 0x74, 0x70, 0x74, 0x00, 0x00, 0x0b, 0x70, 0x00, 0x00, 0x00, 0x14, + 0x63, 0x70, 0x72, 0x74, 0x00, 0x00, 0x0b, 0x84, 0x00, 0x00, 0x00, 0x37, + 0x63, 0x68, 0x61, 0x64, 0x00, 0x00, 0x0b, 0xbc, 0x00, 0x00, 0x00, 0x2c, + 0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, + 0x73, 0x52, 0x47, 0x42, 0x20, 0x49, 0x45, 0x43, 0x36, 0x31, 0x39, 0x36, + 0x36, 0x2d, 0x32, 0x2d, 0x31, 0x20, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x20, + 0x73, 0x63, 0x61, 0x6c, 0x65, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x24, 0xa0, 0x00, 0x00, 0x0f, 0x84, 0x00, 0x00, 0xb6, 0xcf, + 0x63, 0x75, 0x72, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x05, 0x00, 0x0a, 0x00, 0x0f, 0x00, 0x14, 0x00, 0x19, + 0x00, 0x1e, 0x00, 0x23, 0x00, 0x28, 0x00, 0x2d, 0x00, 0x32, 0x00, 0x37, + 0x00, 0x3b, 0x00, 0x40, 0x00, 0x45, 0x00, 0x4a, 0x00, 0x4f, 0x00, 0x54, + 0x00, 0x59, 0x00, 0x5e, 0x00, 0x63, 0x00, 0x68, 0x00, 0x6d, 0x00, 0x72, + 0x00, 0x77, 0x00, 0x7c, 0x00, 0x81, 0x00, 0x86, 0x00, 0x8b, 0x00, 0x90, + 0x00, 0x95, 0x00, 0x9a, 0x00, 0x9f, 0x00, 0xa4, 0x00, 0xa9, 0x00, 0xae, + 0x00, 0xb2, 0x00, 0xb7, 0x00, 0xbc, 0x00, 0xc1, 0x00, 0xc6, 0x00, 0xcb, + 0x00, 0xd0, 0x00, 0xd5, 0x00, 0xdb, 0x00, 0xe0, 0x00, 0xe5, 0x00, 0xeb, + 0x00, 0xf0, 0x00, 0xf6, 0x00, 0xfb, 0x01, 0x01, 0x01, 0x07, 0x01, 0x0d, + 0x01, 0x13, 0x01, 0x19, 0x01, 0x1f, 0x01, 0x25, 0x01, 0x2b, 0x01, 0x32, + 0x01, 0x38, 0x01, 0x3e, 0x01, 0x45, 0x01, 0x4c, 0x01, 0x52, 0x01, 0x59, + 0x01, 0x60, 0x01, 0x67, 0x01, 0x6e, 0x01, 0x75, 0x01, 0x7c, 0x01, 0x83, + 0x01, 0x8b, 0x01, 0x92, 0x01, 0x9a, 0x01, 0xa1, 0x01, 0xa9, 0x01, 0xb1, + 0x01, 0xb9, 0x01, 0xc1, 0x01, 0xc9, 0x01, 0xd1, 0x01, 0xd9, 0x01, 0xe1, + 0x01, 0xe9, 0x01, 0xf2, 0x01, 0xfa, 0x02, 0x03, 0x02, 0x0c, 0x02, 0x14, + 0x02, 0x1d, 0x02, 0x26, 0x02, 0x2f, 0x02, 0x38, 0x02, 0x41, 0x02, 0x4b, + 0x02, 0x54, 0x02, 0x5d, 0x02, 0x67, 0x02, 0x71, 0x02, 0x7a, 0x02, 0x84, + 0x02, 0x8e, 0x02, 0x98, 0x02, 0xa2, 0x02, 0xac, 0x02, 0xb6, 0x02, 0xc1, + 0x02, 0xcb, 0x02, 0xd5, 0x02, 0xe0, 0x02, 0xeb, 0x02, 0xf5, 0x03, 0x00, + 0x03, 0x0b, 0x03, 0x16, 0x03, 0x21, 0x03, 0x2d, 0x03, 0x38, 0x03, 0x43, + 0x03, 0x4f, 0x03, 0x5a, 0x03, 0x66, 0x03, 0x72, 0x03, 0x7e, 0x03, 0x8a, + 0x03, 0x96, 0x03, 0xa2, 0x03, 0xae, 0x03, 0xba, 0x03, 0xc7, 0x03, 0xd3, + 0x03, 0xe0, 0x03, 0xec, 0x03, 0xf9, 0x04, 0x06, 0x04, 0x13, 0x04, 0x20, + 0x04, 0x2d, 0x04, 0x3b, 0x04, 0x48, 0x04, 0x55, 0x04, 0x63, 0x04, 0x71, + 0x04, 0x7e, 0x04, 0x8c, 0x04, 0x9a, 0x04, 0xa8, 0x04, 0xb6, 0x04, 0xc4, + 0x04, 0xd3, 0x04, 0xe1, 0x04, 0xf0, 0x04, 0xfe, 0x05, 0x0d, 0x05, 0x1c, + 0x05, 0x2b, 0x05, 0x3a, 0x05, 0x49, 0x05, 0x58, 0x05, 0x67, 0x05, 0x77, + 0x05, 0x86, 0x05, 0x96, 0x05, 0xa6, 0x05, 0xb5, 0x05, 0xc5, 0x05, 0xd5, + 0x05, 0xe5, 0x05, 0xf6, 0x06, 0x06, 0x06, 0x16, 0x06, 0x27, 0x06, 0x37, + 0x06, 0x48, 0x06, 0x59, 0x06, 0x6a, 0x06, 0x7b, 0x06, 0x8c, 0x06, 0x9d, + 0x06, 0xaf, 0x06, 0xc0, 0x06, 0xd1, 0x06, 0xe3, 0x06, 0xf5, 0x07, 0x07, + 0x07, 0x19, 0x07, 0x2b, 0x07, 0x3d, 0x07, 0x4f, 0x07, 0x61, 0x07, 0x74, + 0x07, 0x86, 0x07, 0x99, 0x07, 0xac, 0x07, 0xbf, 0x07, 0xd2, 0x07, 0xe5, + 0x07, 0xf8, 0x08, 0x0b, 0x08, 0x1f, 0x08, 0x32, 0x08, 0x46, 0x08, 0x5a, + 0x08, 0x6e, 0x08, 0x82, 0x08, 0x96, 0x08, 0xaa, 0x08, 0xbe, 0x08, 0xd2, + 0x08, 0xe7, 0x08, 0xfb, 0x09, 0x10, 0x09, 0x25, 0x09, 0x3a, 0x09, 0x4f, + 0x09, 0x64, 0x09, 0x79, 0x09, 0x8f, 0x09, 0xa4, 0x09, 0xba, 0x09, 0xcf, + 0x09, 0xe5, 0x09, 0xfb, 0x0a, 0x11, 0x0a, 0x27, 0x0a, 0x3d, 0x0a, 0x54, + 0x0a, 0x6a, 0x0a, 0x81, 0x0a, 0x98, 0x0a, 0xae, 0x0a, 0xc5, 0x0a, 0xdc, + 0x0a, 0xf3, 0x0b, 0x0b, 0x0b, 0x22, 0x0b, 0x39, 0x0b, 0x51, 0x0b, 0x69, + 0x0b, 0x80, 0x0b, 0x98, 0x0b, 0xb0, 0x0b, 0xc8, 0x0b, 0xe1, 0x0b, 0xf9, + 0x0c, 0x12, 0x0c, 0x2a, 0x0c, 0x43, 0x0c, 0x5c, 0x0c, 0x75, 0x0c, 0x8e, + 0x0c, 0xa7, 0x0c, 0xc0, 0x0c, 0xd9, 0x0c, 0xf3, 0x0d, 0x0d, 0x0d, 0x26, + 0x0d, 0x40, 0x0d, 0x5a, 0x0d, 0x74, 0x0d, 0x8e, 0x0d, 0xa9, 0x0d, 0xc3, + 0x0d, 0xde, 0x0d, 0xf8, 0x0e, 0x13, 0x0e, 0x2e, 0x0e, 0x49, 0x0e, 0x64, + 0x0e, 0x7f, 0x0e, 0x9b, 0x0e, 0xb6, 0x0e, 0xd2, 0x0e, 0xee, 0x0f, 0x09, + 0x0f, 0x25, 0x0f, 0x41, 0x0f, 0x5e, 0x0f, 0x7a, 0x0f, 0x96, 0x0f, 0xb3, + 0x0f, 0xcf, 0x0f, 0xec, 0x10, 0x09, 0x10, 0x26, 0x10, 0x43, 0x10, 0x61, + 0x10, 0x7e, 0x10, 0x9b, 0x10, 0xb9, 0x10, 0xd7, 0x10, 0xf5, 0x11, 0x13, + 0x11, 0x31, 0x11, 0x4f, 0x11, 0x6d, 0x11, 0x8c, 0x11, 0xaa, 0x11, 0xc9, + 0x11, 0xe8, 0x12, 0x07, 0x12, 0x26, 0x12, 0x45, 0x12, 0x64, 0x12, 0x84, + 0x12, 0xa3, 0x12, 0xc3, 0x12, 0xe3, 0x13, 0x03, 0x13, 0x23, 0x13, 0x43, + 0x13, 0x63, 0x13, 0x83, 0x13, 0xa4, 0x13, 0xc5, 0x13, 0xe5, 0x14, 0x06, + 0x14, 0x27, 0x14, 0x49, 0x14, 0x6a, 0x14, 0x8b, 0x14, 0xad, 0x14, 0xce, + 0x14, 0xf0, 0x15, 0x12, 0x15, 0x34, 0x15, 0x56, 0x15, 0x78, 0x15, 0x9b, + 0x15, 0xbd, 0x15, 0xe0, 0x16, 0x03, 0x16, 0x26, 0x16, 0x49, 0x16, 0x6c, + 0x16, 0x8f, 0x16, 0xb2, 0x16, 0xd6, 0x16, 0xfa, 0x17, 0x1d, 0x17, 0x41, + 0x17, 0x65, 0x17, 0x89, 0x17, 0xae, 0x17, 0xd2, 0x17, 0xf7, 0x18, 0x1b, + 0x18, 0x40, 0x18, 0x65, 0x18, 0x8a, 0x18, 0xaf, 0x18, 0xd5, 0x18, 0xfa, + 0x19, 0x20, 0x19, 0x45, 0x19, 0x6b, 0x19, 0x91, 0x19, 0xb7, 0x19, 0xdd, + 0x1a, 0x04, 0x1a, 0x2a, 0x1a, 0x51, 0x1a, 0x77, 0x1a, 0x9e, 0x1a, 0xc5, + 0x1a, 0xec, 0x1b, 0x14, 0x1b, 0x3b, 0x1b, 0x63, 0x1b, 0x8a, 0x1b, 0xb2, + 0x1b, 0xda, 0x1c, 0x02, 0x1c, 0x2a, 0x1c, 0x52, 0x1c, 0x7b, 0x1c, 0xa3, + 0x1c, 0xcc, 0x1c, 0xf5, 0x1d, 0x1e, 0x1d, 0x47, 0x1d, 0x70, 0x1d, 0x99, + 0x1d, 0xc3, 0x1d, 0xec, 0x1e, 0x16, 0x1e, 0x40, 0x1e, 0x6a, 0x1e, 0x94, + 0x1e, 0xbe, 0x1e, 0xe9, 0x1f, 0x13, 0x1f, 0x3e, 0x1f, 0x69, 0x1f, 0x94, + 0x1f, 0xbf, 0x1f, 0xea, 0x20, 0x15, 0x20, 0x41, 0x20, 0x6c, 0x20, 0x98, + 0x20, 0xc4, 0x20, 0xf0, 0x21, 0x1c, 0x21, 0x48, 0x21, 0x75, 0x21, 0xa1, + 0x21, 0xce, 0x21, 0xfb, 0x22, 0x27, 0x22, 0x55, 0x22, 0x82, 0x22, 0xaf, + 0x22, 0xdd, 0x23, 0x0a, 0x23, 0x38, 0x23, 0x66, 0x23, 0x94, 0x23, 0xc2, + 0x23, 0xf0, 0x24, 0x1f, 0x24, 0x4d, 0x24, 0x7c, 0x24, 0xab, 0x24, 0xda, + 0x25, 0x09, 0x25, 0x38, 0x25, 0x68, 0x25, 0x97, 0x25, 0xc7, 0x25, 0xf7, + 0x26, 0x27, 0x26, 0x57, 0x26, 0x87, 0x26, 0xb7, 0x26, 0xe8, 0x27, 0x18, + 0x27, 0x49, 0x27, 0x7a, 0x27, 0xab, 0x27, 0xdc, 0x28, 0x0d, 0x28, 0x3f, + 0x28, 0x71, 0x28, 0xa2, 0x28, 0xd4, 0x29, 0x06, 0x29, 0x38, 0x29, 0x6b, + 0x29, 0x9d, 0x29, 0xd0, 0x2a, 0x02, 0x2a, 0x35, 0x2a, 0x68, 0x2a, 0x9b, + 0x2a, 0xcf, 0x2b, 0x02, 0x2b, 0x36, 0x2b, 0x69, 0x2b, 0x9d, 0x2b, 0xd1, + 0x2c, 0x05, 0x2c, 0x39, 0x2c, 0x6e, 0x2c, 0xa2, 0x2c, 0xd7, 0x2d, 0x0c, + 0x2d, 0x41, 0x2d, 0x76, 0x2d, 0xab, 0x2d, 0xe1, 0x2e, 0x16, 0x2e, 0x4c, + 0x2e, 0x82, 0x2e, 0xb7, 0x2e, 0xee, 0x2f, 0x24, 0x2f, 0x5a, 0x2f, 0x91, + 0x2f, 0xc7, 0x2f, 0xfe, 0x30, 0x35, 0x30, 0x6c, 0x30, 0xa4, 0x30, 0xdb, + 0x31, 0x12, 0x31, 0x4a, 0x31, 0x82, 0x31, 0xba, 0x31, 0xf2, 0x32, 0x2a, + 0x32, 0x63, 0x32, 0x9b, 0x32, 0xd4, 0x33, 0x0d, 0x33, 0x46, 0x33, 0x7f, + 0x33, 0xb8, 0x33, 0xf1, 0x34, 0x2b, 0x34, 0x65, 0x34, 0x9e, 0x34, 0xd8, + 0x35, 0x13, 0x35, 0x4d, 0x35, 0x87, 0x35, 0xc2, 0x35, 0xfd, 0x36, 0x37, + 0x36, 0x72, 0x36, 0xae, 0x36, 0xe9, 0x37, 0x24, 0x37, 0x60, 0x37, 0x9c, + 0x37, 0xd7, 0x38, 0x14, 0x38, 0x50, 0x38, 0x8c, 0x38, 0xc8, 0x39, 0x05, + 0x39, 0x42, 0x39, 0x7f, 0x39, 0xbc, 0x39, 0xf9, 0x3a, 0x36, 0x3a, 0x74, + 0x3a, 0xb2, 0x3a, 0xef, 0x3b, 0x2d, 0x3b, 0x6b, 0x3b, 0xaa, 0x3b, 0xe8, + 0x3c, 0x27, 0x3c, 0x65, 0x3c, 0xa4, 0x3c, 0xe3, 0x3d, 0x22, 0x3d, 0x61, + 0x3d, 0xa1, 0x3d, 0xe0, 0x3e, 0x20, 0x3e, 0x60, 0x3e, 0xa0, 0x3e, 0xe0, + 0x3f, 0x21, 0x3f, 0x61, 0x3f, 0xa2, 0x3f, 0xe2, 0x40, 0x23, 0x40, 0x64, + 0x40, 0xa6, 0x40, 0xe7, 0x41, 0x29, 0x41, 0x6a, 0x41, 0xac, 0x41, 0xee, + 0x42, 0x30, 0x42, 0x72, 0x42, 0xb5, 0x42, 0xf7, 0x43, 0x3a, 0x43, 0x7d, + 0x43, 0xc0, 0x44, 0x03, 0x44, 0x47, 0x44, 0x8a, 0x44, 0xce, 0x45, 0x12, + 0x45, 0x55, 0x45, 0x9a, 0x45, 0xde, 0x46, 0x22, 0x46, 0x67, 0x46, 0xab, + 0x46, 0xf0, 0x47, 0x35, 0x47, 0x7b, 0x47, 0xc0, 0x48, 0x05, 0x48, 0x4b, + 0x48, 0x91, 0x48, 0xd7, 0x49, 0x1d, 0x49, 0x63, 0x49, 0xa9, 0x49, 0xf0, + 0x4a, 0x37, 0x4a, 0x7d, 0x4a, 0xc4, 0x4b, 0x0c, 0x4b, 0x53, 0x4b, 0x9a, + 0x4b, 0xe2, 0x4c, 0x2a, 0x4c, 0x72, 0x4c, 0xba, 0x4d, 0x02, 0x4d, 0x4a, + 0x4d, 0x93, 0x4d, 0xdc, 0x4e, 0x25, 0x4e, 0x6e, 0x4e, 0xb7, 0x4f, 0x00, + 0x4f, 0x49, 0x4f, 0x93, 0x4f, 0xdd, 0x50, 0x27, 0x50, 0x71, 0x50, 0xbb, + 0x51, 0x06, 0x51, 0x50, 0x51, 0x9b, 0x51, 0xe6, 0x52, 0x31, 0x52, 0x7c, + 0x52, 0xc7, 0x53, 0x13, 0x53, 0x5f, 0x53, 0xaa, 0x53, 0xf6, 0x54, 0x42, + 0x54, 0x8f, 0x54, 0xdb, 0x55, 0x28, 0x55, 0x75, 0x55, 0xc2, 0x56, 0x0f, + 0x56, 0x5c, 0x56, 0xa9, 0x56, 0xf7, 0x57, 0x44, 0x57, 0x92, 0x57, 0xe0, + 0x58, 0x2f, 0x58, 0x7d, 0x58, 0xcb, 0x59, 0x1a, 0x59, 0x69, 0x59, 0xb8, + 0x5a, 0x07, 0x5a, 0x56, 0x5a, 0xa6, 0x5a, 0xf5, 0x5b, 0x45, 0x5b, 0x95, + 0x5b, 0xe5, 0x5c, 0x35, 0x5c, 0x86, 0x5c, 0xd6, 0x5d, 0x27, 0x5d, 0x78, + 0x5d, 0xc9, 0x5e, 0x1a, 0x5e, 0x6c, 0x5e, 0xbd, 0x5f, 0x0f, 0x5f, 0x61, + 0x5f, 0xb3, 0x60, 0x05, 0x60, 0x57, 0x60, 0xaa, 0x60, 0xfc, 0x61, 0x4f, + 0x61, 0xa2, 0x61, 0xf5, 0x62, 0x49, 0x62, 0x9c, 0x62, 0xf0, 0x63, 0x43, + 0x63, 0x97, 0x63, 0xeb, 0x64, 0x40, 0x64, 0x94, 0x64, 0xe9, 0x65, 0x3d, + 0x65, 0x92, 0x65, 0xe7, 0x66, 0x3d, 0x66, 0x92, 0x66, 0xe8, 0x67, 0x3d, + 0x67, 0x93, 0x67, 0xe9, 0x68, 0x3f, 0x68, 0x96, 0x68, 0xec, 0x69, 0x43, + 0x69, 0x9a, 0x69, 0xf1, 0x6a, 0x48, 0x6a, 0x9f, 0x6a, 0xf7, 0x6b, 0x4f, + 0x6b, 0xa7, 0x6b, 0xff, 0x6c, 0x57, 0x6c, 0xaf, 0x6d, 0x08, 0x6d, 0x60, + 0x6d, 0xb9, 0x6e, 0x12, 0x6e, 0x6b, 0x6e, 0xc4, 0x6f, 0x1e, 0x6f, 0x78, + 0x6f, 0xd1, 0x70, 0x2b, 0x70, 0x86, 0x70, 0xe0, 0x71, 0x3a, 0x71, 0x95, + 0x71, 0xf0, 0x72, 0x4b, 0x72, 0xa6, 0x73, 0x01, 0x73, 0x5d, 0x73, 0xb8, + 0x74, 0x14, 0x74, 0x70, 0x74, 0xcc, 0x75, 0x28, 0x75, 0x85, 0x75, 0xe1, + 0x76, 0x3e, 0x76, 0x9b, 0x76, 0xf8, 0x77, 0x56, 0x77, 0xb3, 0x78, 0x11, + 0x78, 0x6e, 0x78, 0xcc, 0x79, 0x2a, 0x79, 0x89, 0x79, 0xe7, 0x7a, 0x46, + 0x7a, 0xa5, 0x7b, 0x04, 0x7b, 0x63, 0x7b, 0xc2, 0x7c, 0x21, 0x7c, 0x81, + 0x7c, 0xe1, 0x7d, 0x41, 0x7d, 0xa1, 0x7e, 0x01, 0x7e, 0x62, 0x7e, 0xc2, + 0x7f, 0x23, 0x7f, 0x84, 0x7f, 0xe5, 0x80, 0x47, 0x80, 0xa8, 0x81, 0x0a, + 0x81, 0x6b, 0x81, 0xcd, 0x82, 0x30, 0x82, 0x92, 0x82, 0xf4, 0x83, 0x57, + 0x83, 0xba, 0x84, 0x1d, 0x84, 0x80, 0x84, 0xe3, 0x85, 0x47, 0x85, 0xab, + 0x86, 0x0e, 0x86, 0x72, 0x86, 0xd7, 0x87, 0x3b, 0x87, 0x9f, 0x88, 0x04, + 0x88, 0x69, 0x88, 0xce, 0x89, 0x33, 0x89, 0x99, 0x89, 0xfe, 0x8a, 0x64, + 0x8a, 0xca, 0x8b, 0x30, 0x8b, 0x96, 0x8b, 0xfc, 0x8c, 0x63, 0x8c, 0xca, + 0x8d, 0x31, 0x8d, 0x98, 0x8d, 0xff, 0x8e, 0x66, 0x8e, 0xce, 0x8f, 0x36, + 0x8f, 0x9e, 0x90, 0x06, 0x90, 0x6e, 0x90, 0xd6, 0x91, 0x3f, 0x91, 0xa8, + 0x92, 0x11, 0x92, 0x7a, 0x92, 0xe3, 0x93, 0x4d, 0x93, 0xb6, 0x94, 0x20, + 0x94, 0x8a, 0x94, 0xf4, 0x95, 0x5f, 0x95, 0xc9, 0x96, 0x34, 0x96, 0x9f, + 0x97, 0x0a, 0x97, 0x75, 0x97, 0xe0, 0x98, 0x4c, 0x98, 0xb8, 0x99, 0x24, + 0x99, 0x90, 0x99, 0xfc, 0x9a, 0x68, 0x9a, 0xd5, 0x9b, 0x42, 0x9b, 0xaf, + 0x9c, 0x1c, 0x9c, 0x89, 0x9c, 0xf7, 0x9d, 0x64, 0x9d, 0xd2, 0x9e, 0x40, + 0x9e, 0xae, 0x9f, 0x1d, 0x9f, 0x8b, 0x9f, 0xfa, 0xa0, 0x69, 0xa0, 0xd8, + 0xa1, 0x47, 0xa1, 0xb6, 0xa2, 0x26, 0xa2, 0x96, 0xa3, 0x06, 0xa3, 0x76, + 0xa3, 0xe6, 0xa4, 0x56, 0xa4, 0xc7, 0xa5, 0x38, 0xa5, 0xa9, 0xa6, 0x1a, + 0xa6, 0x8b, 0xa6, 0xfd, 0xa7, 0x6e, 0xa7, 0xe0, 0xa8, 0x52, 0xa8, 0xc4, + 0xa9, 0x37, 0xa9, 0xa9, 0xaa, 0x1c, 0xaa, 0x8f, 0xab, 0x02, 0xab, 0x75, + 0xab, 0xe9, 0xac, 0x5c, 0xac, 0xd0, 0xad, 0x44, 0xad, 0xb8, 0xae, 0x2d, + 0xae, 0xa1, 0xaf, 0x16, 0xaf, 0x8b, 0xb0, 0x00, 0xb0, 0x75, 0xb0, 0xea, + 0xb1, 0x60, 0xb1, 0xd6, 0xb2, 0x4b, 0xb2, 0xc2, 0xb3, 0x38, 0xb3, 0xae, + 0xb4, 0x25, 0xb4, 0x9c, 0xb5, 0x13, 0xb5, 0x8a, 0xb6, 0x01, 0xb6, 0x79, + 0xb6, 0xf0, 0xb7, 0x68, 0xb7, 0xe0, 0xb8, 0x59, 0xb8, 0xd1, 0xb9, 0x4a, + 0xb9, 0xc2, 0xba, 0x3b, 0xba, 0xb5, 0xbb, 0x2e, 0xbb, 0xa7, 0xbc, 0x21, + 0xbc, 0x9b, 0xbd, 0x15, 0xbd, 0x8f, 0xbe, 0x0a, 0xbe, 0x84, 0xbe, 0xff, + 0xbf, 0x7a, 0xbf, 0xf5, 0xc0, 0x70, 0xc0, 0xec, 0xc1, 0x67, 0xc1, 0xe3, + 0xc2, 0x5f, 0xc2, 0xdb, 0xc3, 0x58, 0xc3, 0xd4, 0xc4, 0x51, 0xc4, 0xce, + 0xc5, 0x4b, 0xc5, 0xc8, 0xc6, 0x46, 0xc6, 0xc3, 0xc7, 0x41, 0xc7, 0xbf, + 0xc8, 0x3d, 0xc8, 0xbc, 0xc9, 0x3a, 0xc9, 0xb9, 0xca, 0x38, 0xca, 0xb7, + 0xcb, 0x36, 0xcb, 0xb6, 0xcc, 0x35, 0xcc, 0xb5, 0xcd, 0x35, 0xcd, 0xb5, + 0xce, 0x36, 0xce, 0xb6, 0xcf, 0x37, 0xcf, 0xb8, 0xd0, 0x39, 0xd0, 0xba, + 0xd1, 0x3c, 0xd1, 0xbe, 0xd2, 0x3f, 0xd2, 0xc1, 0xd3, 0x44, 0xd3, 0xc6, + 0xd4, 0x49, 0xd4, 0xcb, 0xd5, 0x4e, 0xd5, 0xd1, 0xd6, 0x55, 0xd6, 0xd8, + 0xd7, 0x5c, 0xd7, 0xe0, 0xd8, 0x64, 0xd8, 0xe8, 0xd9, 0x6c, 0xd9, 0xf1, + 0xda, 0x76, 0xda, 0xfb, 0xdb, 0x80, 0xdc, 0x05, 0xdc, 0x8a, 0xdd, 0x10, + 0xdd, 0x96, 0xde, 0x1c, 0xde, 0xa2, 0xdf, 0x29, 0xdf, 0xaf, 0xe0, 0x36, + 0xe0, 0xbd, 0xe1, 0x44, 0xe1, 0xcc, 0xe2, 0x53, 0xe2, 0xdb, 0xe3, 0x63, + 0xe3, 0xeb, 0xe4, 0x73, 0xe4, 0xfc, 0xe5, 0x84, 0xe6, 0x0d, 0xe6, 0x96, + 0xe7, 0x1f, 0xe7, 0xa9, 0xe8, 0x32, 0xe8, 0xbc, 0xe9, 0x46, 0xe9, 0xd0, + 0xea, 0x5b, 0xea, 0xe5, 0xeb, 0x70, 0xeb, 0xfb, 0xec, 0x86, 0xed, 0x11, + 0xed, 0x9c, 0xee, 0x28, 0xee, 0xb4, 0xef, 0x40, 0xef, 0xcc, 0xf0, 0x58, + 0xf0, 0xe5, 0xf1, 0x72, 0xf1, 0xff, 0xf2, 0x8c, 0xf3, 0x19, 0xf3, 0xa7, + 0xf4, 0x34, 0xf4, 0xc2, 0xf5, 0x50, 0xf5, 0xde, 0xf6, 0x6d, 0xf6, 0xfb, + 0xf7, 0x8a, 0xf8, 0x19, 0xf8, 0xa8, 0xf9, 0x38, 0xf9, 0xc7, 0xfa, 0x57, + 0xfa, 0xe7, 0xfb, 0x77, 0xfc, 0x07, 0xfc, 0x98, 0xfd, 0x29, 0xfd, 0xba, + 0xfe, 0x4b, 0xfe, 0xdc, 0xff, 0x6d, 0xff, 0xff, 0x64, 0x65, 0x73, 0x63, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2e, 0x49, 0x45, 0x43, 0x20, + 0x36, 0x31, 0x39, 0x36, 0x36, 0x2d, 0x32, 0x2d, 0x31, 0x20, 0x44, 0x65, + 0x66, 0x61, 0x75, 0x6c, 0x74, 0x20, 0x52, 0x47, 0x42, 0x20, 0x43, 0x6f, + 0x6c, 0x6f, 0x75, 0x72, 0x20, 0x53, 0x70, 0x61, 0x63, 0x65, 0x20, 0x2d, + 0x20, 0x73, 0x52, 0x47, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x99, + 0x00, 0x00, 0xb7, 0x85, 0x00, 0x00, 0x18, 0xda, 0x58, 0x59, 0x5a, 0x20, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x6d, 0x65, 0x61, 0x73, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x16, 0x00, 0x00, 0x03, 0x33, 0x00, 0x00, 0x02, 0xa4, + 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6f, 0xa2, + 0x00, 0x00, 0x38, 0xf5, 0x00, 0x00, 0x03, 0x90, 0x73, 0x69, 0x67, 0x20, + 0x00, 0x00, 0x00, 0x00, 0x43, 0x52, 0x54, 0x20, 0x64, 0x65, 0x73, 0x63, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x52, 0x65, 0x66, 0x65, + 0x72, 0x65, 0x6e, 0x63, 0x65, 0x20, 0x56, 0x69, 0x65, 0x77, 0x69, 0x6e, + 0x67, 0x20, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x20, + 0x69, 0x6e, 0x20, 0x49, 0x45, 0x43, 0x20, 0x36, 0x31, 0x39, 0x36, 0x36, + 0x2d, 0x32, 0x2d, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf6, 0xd6, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xd3, 0x2d, 0x74, 0x65, 0x78, 0x74, + 0x00, 0x00, 0x00, 0x00, 0x43, 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, + 0x74, 0x20, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x61, 0x6c, 0x20, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x43, 0x6f, + 0x6e, 0x73, 0x6f, 0x72, 0x74, 0x69, 0x75, 0x6d, 0x2c, 0x20, 0x32, 0x30, + 0x30, 0x39, 0x00, 0x00, 0x73, 0x66, 0x33, 0x32, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x0c, 0x44, 0x00, 0x00, 0x05, 0xdf, 0xff, 0xff, 0xf3, 0x26, + 0x00, 0x00, 0x07, 0x94, 0x00, 0x00, 0xfd, 0x8f, 0xff, 0xff, 0xfb, 0xa1, + 0xff, 0xff, 0xfd, 0xa2, 0x00, 0x00, 0x03, 0xdb, 0x00, 0x00, 0xc0, 0x75, + } + + genericGrayGamma22ICCProfile = []byte{ + 0x00, 0x00, 0x0e, 0x04, 0x61, 0x70, 0x70, 0x6c, 0x02, 0x00, 0x00, 0x00, + 0x6d, 0x6e, 0x74, 0x72, 0x47, 0x52, 0x41, 0x59, 0x58, 0x59, 0x5a, 0x20, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x61, 0x63, 0x73, 0x70, 0x41, 0x50, 0x50, 0x4c, 0x00, 0x00, 0x00, 0x00, + 0x6e, 0x6f, 0x6e, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf6, 0xd6, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xd3, 0x2d, 0x61, 0x70, 0x70, 0x6c, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, + 0x6b, 0x54, 0x52, 0x43, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x08, 0x0c, + 0x77, 0x74, 0x70, 0x74, 0x00, 0x00, 0x08, 0xcc, 0x00, 0x00, 0x00, 0x14, + 0x63, 0x70, 0x72, 0x74, 0x00, 0x00, 0x08, 0xe0, 0x00, 0x00, 0x00, 0x23, + 0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x09, 0x04, 0x00, 0x00, 0x00, 0x79, + 0x64, 0x73, 0x63, 0x6d, 0x00, 0x00, 0x09, 0x80, 0x00, 0x00, 0x04, 0x82, + 0x63, 0x75, 0x72, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x05, 0x00, 0x0a, 0x00, 0x0f, 0x00, 0x14, 0x00, 0x19, + 0x00, 0x1e, 0x00, 0x23, 0x00, 0x28, 0x00, 0x2d, 0x00, 0x32, 0x00, 0x37, + 0x00, 0x3b, 0x00, 0x40, 0x00, 0x45, 0x00, 0x4a, 0x00, 0x4f, 0x00, 0x54, + 0x00, 0x59, 0x00, 0x5e, 0x00, 0x63, 0x00, 0x68, 0x00, 0x6d, 0x00, 0x72, + 0x00, 0x77, 0x00, 0x7c, 0x00, 0x81, 0x00, 0x86, 0x00, 0x8b, 0x00, 0x90, + 0x00, 0x95, 0x00, 0x9a, 0x00, 0x9f, 0x00, 0xa4, 0x00, 0xa9, 0x00, 0xae, + 0x00, 0xb2, 0x00, 0xb7, 0x00, 0xbc, 0x00, 0xc1, 0x00, 0xc6, 0x00, 0xcb, + 0x00, 0xd0, 0x00, 0xd5, 0x00, 0xdb, 0x00, 0xe0, 0x00, 0xe5, 0x00, 0xeb, + 0x00, 0xf0, 0x00, 0xf6, 0x00, 0xfb, 0x01, 0x01, 0x01, 0x07, 0x01, 0x0d, + 0x01, 0x13, 0x01, 0x19, 0x01, 0x1f, 0x01, 0x25, 0x01, 0x2b, 0x01, 0x32, + 0x01, 0x38, 0x01, 0x3e, 0x01, 0x45, 0x01, 0x4c, 0x01, 0x52, 0x01, 0x59, + 0x01, 0x60, 0x01, 0x67, 0x01, 0x6e, 0x01, 0x75, 0x01, 0x7c, 0x01, 0x83, + 0x01, 0x8b, 0x01, 0x92, 0x01, 0x9a, 0x01, 0xa1, 0x01, 0xa9, 0x01, 0xb1, + 0x01, 0xb9, 0x01, 0xc1, 0x01, 0xc9, 0x01, 0xd1, 0x01, 0xd9, 0x01, 0xe1, + 0x01, 0xe9, 0x01, 0xf2, 0x01, 0xfa, 0x02, 0x03, 0x02, 0x0c, 0x02, 0x14, + 0x02, 0x1d, 0x02, 0x26, 0x02, 0x2f, 0x02, 0x38, 0x02, 0x41, 0x02, 0x4b, + 0x02, 0x54, 0x02, 0x5d, 0x02, 0x67, 0x02, 0x71, 0x02, 0x7a, 0x02, 0x84, + 0x02, 0x8e, 0x02, 0x98, 0x02, 0xa2, 0x02, 0xac, 0x02, 0xb6, 0x02, 0xc1, + 0x02, 0xcb, 0x02, 0xd5, 0x02, 0xe0, 0x02, 0xeb, 0x02, 0xf5, 0x03, 0x00, + 0x03, 0x0b, 0x03, 0x16, 0x03, 0x21, 0x03, 0x2d, 0x03, 0x38, 0x03, 0x43, + 0x03, 0x4f, 0x03, 0x5a, 0x03, 0x66, 0x03, 0x72, 0x03, 0x7e, 0x03, 0x8a, + 0x03, 0x96, 0x03, 0xa2, 0x03, 0xae, 0x03, 0xba, 0x03, 0xc7, 0x03, 0xd3, + 0x03, 0xe0, 0x03, 0xec, 0x03, 0xf9, 0x04, 0x06, 0x04, 0x13, 0x04, 0x20, + 0x04, 0x2d, 0x04, 0x3b, 0x04, 0x48, 0x04, 0x55, 0x04, 0x63, 0x04, 0x71, + 0x04, 0x7e, 0x04, 0x8c, 0x04, 0x9a, 0x04, 0xa8, 0x04, 0xb6, 0x04, 0xc4, + 0x04, 0xd3, 0x04, 0xe1, 0x04, 0xf0, 0x04, 0xfe, 0x05, 0x0d, 0x05, 0x1c, + 0x05, 0x2b, 0x05, 0x3a, 0x05, 0x49, 0x05, 0x58, 0x05, 0x67, 0x05, 0x77, + 0x05, 0x86, 0x05, 0x96, 0x05, 0xa6, 0x05, 0xb5, 0x05, 0xc5, 0x05, 0xd5, + 0x05, 0xe5, 0x05, 0xf6, 0x06, 0x06, 0x06, 0x16, 0x06, 0x27, 0x06, 0x37, + 0x06, 0x48, 0x06, 0x59, 0x06, 0x6a, 0x06, 0x7b, 0x06, 0x8c, 0x06, 0x9d, + 0x06, 0xaf, 0x06, 0xc0, 0x06, 0xd1, 0x06, 0xe3, 0x06, 0xf5, 0x07, 0x07, + 0x07, 0x19, 0x07, 0x2b, 0x07, 0x3d, 0x07, 0x4f, 0x07, 0x61, 0x07, 0x74, + 0x07, 0x86, 0x07, 0x99, 0x07, 0xac, 0x07, 0xbf, 0x07, 0xd2, 0x07, 0xe5, + 0x07, 0xf8, 0x08, 0x0b, 0x08, 0x1f, 0x08, 0x32, 0x08, 0x46, 0x08, 0x5a, + 0x08, 0x6e, 0x08, 0x82, 0x08, 0x96, 0x08, 0xaa, 0x08, 0xbe, 0x08, 0xd2, + 0x08, 0xe7, 0x08, 0xfb, 0x09, 0x10, 0x09, 0x25, 0x09, 0x3a, 0x09, 0x4f, + 0x09, 0x64, 0x09, 0x79, 0x09, 0x8f, 0x09, 0xa4, 0x09, 0xba, 0x09, 0xcf, + 0x09, 0xe5, 0x09, 0xfb, 0x0a, 0x11, 0x0a, 0x27, 0x0a, 0x3d, 0x0a, 0x54, + 0x0a, 0x6a, 0x0a, 0x81, 0x0a, 0x98, 0x0a, 0xae, 0x0a, 0xc5, 0x0a, 0xdc, + 0x0a, 0xf3, 0x0b, 0x0b, 0x0b, 0x22, 0x0b, 0x39, 0x0b, 0x51, 0x0b, 0x69, + 0x0b, 0x80, 0x0b, 0x98, 0x0b, 0xb0, 0x0b, 0xc8, 0x0b, 0xe1, 0x0b, 0xf9, + 0x0c, 0x12, 0x0c, 0x2a, 0x0c, 0x43, 0x0c, 0x5c, 0x0c, 0x75, 0x0c, 0x8e, + 0x0c, 0xa7, 0x0c, 0xc0, 0x0c, 0xd9, 0x0c, 0xf3, 0x0d, 0x0d, 0x0d, 0x26, + 0x0d, 0x40, 0x0d, 0x5a, 0x0d, 0x74, 0x0d, 0x8e, 0x0d, 0xa9, 0x0d, 0xc3, + 0x0d, 0xde, 0x0d, 0xf8, 0x0e, 0x13, 0x0e, 0x2e, 0x0e, 0x49, 0x0e, 0x64, + 0x0e, 0x7f, 0x0e, 0x9b, 0x0e, 0xb6, 0x0e, 0xd2, 0x0e, 0xee, 0x0f, 0x09, + 0x0f, 0x25, 0x0f, 0x41, 0x0f, 0x5e, 0x0f, 0x7a, 0x0f, 0x96, 0x0f, 0xb3, + 0x0f, 0xcf, 0x0f, 0xec, 0x10, 0x09, 0x10, 0x26, 0x10, 0x43, 0x10, 0x61, + 0x10, 0x7e, 0x10, 0x9b, 0x10, 0xb9, 0x10, 0xd7, 0x10, 0xf5, 0x11, 0x13, + 0x11, 0x31, 0x11, 0x4f, 0x11, 0x6d, 0x11, 0x8c, 0x11, 0xaa, 0x11, 0xc9, + 0x11, 0xe8, 0x12, 0x07, 0x12, 0x26, 0x12, 0x45, 0x12, 0x64, 0x12, 0x84, + 0x12, 0xa3, 0x12, 0xc3, 0x12, 0xe3, 0x13, 0x03, 0x13, 0x23, 0x13, 0x43, + 0x13, 0x63, 0x13, 0x83, 0x13, 0xa4, 0x13, 0xc5, 0x13, 0xe5, 0x14, 0x06, + 0x14, 0x27, 0x14, 0x49, 0x14, 0x6a, 0x14, 0x8b, 0x14, 0xad, 0x14, 0xce, + 0x14, 0xf0, 0x15, 0x12, 0x15, 0x34, 0x15, 0x56, 0x15, 0x78, 0x15, 0x9b, + 0x15, 0xbd, 0x15, 0xe0, 0x16, 0x03, 0x16, 0x26, 0x16, 0x49, 0x16, 0x6c, + 0x16, 0x8f, 0x16, 0xb2, 0x16, 0xd6, 0x16, 0xfa, 0x17, 0x1d, 0x17, 0x41, + 0x17, 0x65, 0x17, 0x89, 0x17, 0xae, 0x17, 0xd2, 0x17, 0xf7, 0x18, 0x1b, + 0x18, 0x40, 0x18, 0x65, 0x18, 0x8a, 0x18, 0xaf, 0x18, 0xd5, 0x18, 0xfa, + 0x19, 0x20, 0x19, 0x45, 0x19, 0x6b, 0x19, 0x91, 0x19, 0xb7, 0x19, 0xdd, + 0x1a, 0x04, 0x1a, 0x2a, 0x1a, 0x51, 0x1a, 0x77, 0x1a, 0x9e, 0x1a, 0xc5, + 0x1a, 0xec, 0x1b, 0x14, 0x1b, 0x3b, 0x1b, 0x63, 0x1b, 0x8a, 0x1b, 0xb2, + 0x1b, 0xda, 0x1c, 0x02, 0x1c, 0x2a, 0x1c, 0x52, 0x1c, 0x7b, 0x1c, 0xa3, + 0x1c, 0xcc, 0x1c, 0xf5, 0x1d, 0x1e, 0x1d, 0x47, 0x1d, 0x70, 0x1d, 0x99, + 0x1d, 0xc3, 0x1d, 0xec, 0x1e, 0x16, 0x1e, 0x40, 0x1e, 0x6a, 0x1e, 0x94, + 0x1e, 0xbe, 0x1e, 0xe9, 0x1f, 0x13, 0x1f, 0x3e, 0x1f, 0x69, 0x1f, 0x94, + 0x1f, 0xbf, 0x1f, 0xea, 0x20, 0x15, 0x20, 0x41, 0x20, 0x6c, 0x20, 0x98, + 0x20, 0xc4, 0x20, 0xf0, 0x21, 0x1c, 0x21, 0x48, 0x21, 0x75, 0x21, 0xa1, + 0x21, 0xce, 0x21, 0xfb, 0x22, 0x27, 0x22, 0x55, 0x22, 0x82, 0x22, 0xaf, + 0x22, 0xdd, 0x23, 0x0a, 0x23, 0x38, 0x23, 0x66, 0x23, 0x94, 0x23, 0xc2, + 0x23, 0xf0, 0x24, 0x1f, 0x24, 0x4d, 0x24, 0x7c, 0x24, 0xab, 0x24, 0xda, + 0x25, 0x09, 0x25, 0x38, 0x25, 0x68, 0x25, 0x97, 0x25, 0xc7, 0x25, 0xf7, + 0x26, 0x27, 0x26, 0x57, 0x26, 0x87, 0x26, 0xb7, 0x26, 0xe8, 0x27, 0x18, + 0x27, 0x49, 0x27, 0x7a, 0x27, 0xab, 0x27, 0xdc, 0x28, 0x0d, 0x28, 0x3f, + 0x28, 0x71, 0x28, 0xa2, 0x28, 0xd4, 0x29, 0x06, 0x29, 0x38, 0x29, 0x6b, + 0x29, 0x9d, 0x29, 0xd0, 0x2a, 0x02, 0x2a, 0x35, 0x2a, 0x68, 0x2a, 0x9b, + 0x2a, 0xcf, 0x2b, 0x02, 0x2b, 0x36, 0x2b, 0x69, 0x2b, 0x9d, 0x2b, 0xd1, + 0x2c, 0x05, 0x2c, 0x39, 0x2c, 0x6e, 0x2c, 0xa2, 0x2c, 0xd7, 0x2d, 0x0c, + 0x2d, 0x41, 0x2d, 0x76, 0x2d, 0xab, 0x2d, 0xe1, 0x2e, 0x16, 0x2e, 0x4c, + 0x2e, 0x82, 0x2e, 0xb7, 0x2e, 0xee, 0x2f, 0x24, 0x2f, 0x5a, 0x2f, 0x91, + 0x2f, 0xc7, 0x2f, 0xfe, 0x30, 0x35, 0x30, 0x6c, 0x30, 0xa4, 0x30, 0xdb, + 0x31, 0x12, 0x31, 0x4a, 0x31, 0x82, 0x31, 0xba, 0x31, 0xf2, 0x32, 0x2a, + 0x32, 0x63, 0x32, 0x9b, 0x32, 0xd4, 0x33, 0x0d, 0x33, 0x46, 0x33, 0x7f, + 0x33, 0xb8, 0x33, 0xf1, 0x34, 0x2b, 0x34, 0x65, 0x34, 0x9e, 0x34, 0xd8, + 0x35, 0x13, 0x35, 0x4d, 0x35, 0x87, 0x35, 0xc2, 0x35, 0xfd, 0x36, 0x37, + 0x36, 0x72, 0x36, 0xae, 0x36, 0xe9, 0x37, 0x24, 0x37, 0x60, 0x37, 0x9c, + 0x37, 0xd7, 0x38, 0x14, 0x38, 0x50, 0x38, 0x8c, 0x38, 0xc8, 0x39, 0x05, + 0x39, 0x42, 0x39, 0x7f, 0x39, 0xbc, 0x39, 0xf9, 0x3a, 0x36, 0x3a, 0x74, + 0x3a, 0xb2, 0x3a, 0xef, 0x3b, 0x2d, 0x3b, 0x6b, 0x3b, 0xaa, 0x3b, 0xe8, + 0x3c, 0x27, 0x3c, 0x65, 0x3c, 0xa4, 0x3c, 0xe3, 0x3d, 0x22, 0x3d, 0x61, + 0x3d, 0xa1, 0x3d, 0xe0, 0x3e, 0x20, 0x3e, 0x60, 0x3e, 0xa0, 0x3e, 0xe0, + 0x3f, 0x21, 0x3f, 0x61, 0x3f, 0xa2, 0x3f, 0xe2, 0x40, 0x23, 0x40, 0x64, + 0x40, 0xa6, 0x40, 0xe7, 0x41, 0x29, 0x41, 0x6a, 0x41, 0xac, 0x41, 0xee, + 0x42, 0x30, 0x42, 0x72, 0x42, 0xb5, 0x42, 0xf7, 0x43, 0x3a, 0x43, 0x7d, + 0x43, 0xc0, 0x44, 0x03, 0x44, 0x47, 0x44, 0x8a, 0x44, 0xce, 0x45, 0x12, + 0x45, 0x55, 0x45, 0x9a, 0x45, 0xde, 0x46, 0x22, 0x46, 0x67, 0x46, 0xab, + 0x46, 0xf0, 0x47, 0x35, 0x47, 0x7b, 0x47, 0xc0, 0x48, 0x05, 0x48, 0x4b, + 0x48, 0x91, 0x48, 0xd7, 0x49, 0x1d, 0x49, 0x63, 0x49, 0xa9, 0x49, 0xf0, + 0x4a, 0x37, 0x4a, 0x7d, 0x4a, 0xc4, 0x4b, 0x0c, 0x4b, 0x53, 0x4b, 0x9a, + 0x4b, 0xe2, 0x4c, 0x2a, 0x4c, 0x72, 0x4c, 0xba, 0x4d, 0x02, 0x4d, 0x4a, + 0x4d, 0x93, 0x4d, 0xdc, 0x4e, 0x25, 0x4e, 0x6e, 0x4e, 0xb7, 0x4f, 0x00, + 0x4f, 0x49, 0x4f, 0x93, 0x4f, 0xdd, 0x50, 0x27, 0x50, 0x71, 0x50, 0xbb, + 0x51, 0x06, 0x51, 0x50, 0x51, 0x9b, 0x51, 0xe6, 0x52, 0x31, 0x52, 0x7c, + 0x52, 0xc7, 0x53, 0x13, 0x53, 0x5f, 0x53, 0xaa, 0x53, 0xf6, 0x54, 0x42, + 0x54, 0x8f, 0x54, 0xdb, 0x55, 0x28, 0x55, 0x75, 0x55, 0xc2, 0x56, 0x0f, + 0x56, 0x5c, 0x56, 0xa9, 0x56, 0xf7, 0x57, 0x44, 0x57, 0x92, 0x57, 0xe0, + 0x58, 0x2f, 0x58, 0x7d, 0x58, 0xcb, 0x59, 0x1a, 0x59, 0x69, 0x59, 0xb8, + 0x5a, 0x07, 0x5a, 0x56, 0x5a, 0xa6, 0x5a, 0xf5, 0x5b, 0x45, 0x5b, 0x95, + 0x5b, 0xe5, 0x5c, 0x35, 0x5c, 0x86, 0x5c, 0xd6, 0x5d, 0x27, 0x5d, 0x78, + 0x5d, 0xc9, 0x5e, 0x1a, 0x5e, 0x6c, 0x5e, 0xbd, 0x5f, 0x0f, 0x5f, 0x61, + 0x5f, 0xb3, 0x60, 0x05, 0x60, 0x57, 0x60, 0xaa, 0x60, 0xfc, 0x61, 0x4f, + 0x61, 0xa2, 0x61, 0xf5, 0x62, 0x49, 0x62, 0x9c, 0x62, 0xf0, 0x63, 0x43, + 0x63, 0x97, 0x63, 0xeb, 0x64, 0x40, 0x64, 0x94, 0x64, 0xe9, 0x65, 0x3d, + 0x65, 0x92, 0x65, 0xe7, 0x66, 0x3d, 0x66, 0x92, 0x66, 0xe8, 0x67, 0x3d, + 0x67, 0x93, 0x67, 0xe9, 0x68, 0x3f, 0x68, 0x96, 0x68, 0xec, 0x69, 0x43, + 0x69, 0x9a, 0x69, 0xf1, 0x6a, 0x48, 0x6a, 0x9f, 0x6a, 0xf7, 0x6b, 0x4f, + 0x6b, 0xa7, 0x6b, 0xff, 0x6c, 0x57, 0x6c, 0xaf, 0x6d, 0x08, 0x6d, 0x60, + 0x6d, 0xb9, 0x6e, 0x12, 0x6e, 0x6b, 0x6e, 0xc4, 0x6f, 0x1e, 0x6f, 0x78, + 0x6f, 0xd1, 0x70, 0x2b, 0x70, 0x86, 0x70, 0xe0, 0x71, 0x3a, 0x71, 0x95, + 0x71, 0xf0, 0x72, 0x4b, 0x72, 0xa6, 0x73, 0x01, 0x73, 0x5d, 0x73, 0xb8, + 0x74, 0x14, 0x74, 0x70, 0x74, 0xcc, 0x75, 0x28, 0x75, 0x85, 0x75, 0xe1, + 0x76, 0x3e, 0x76, 0x9b, 0x76, 0xf8, 0x77, 0x56, 0x77, 0xb3, 0x78, 0x11, + 0x78, 0x6e, 0x78, 0xcc, 0x79, 0x2a, 0x79, 0x89, 0x79, 0xe7, 0x7a, 0x46, + 0x7a, 0xa5, 0x7b, 0x04, 0x7b, 0x63, 0x7b, 0xc2, 0x7c, 0x21, 0x7c, 0x81, + 0x7c, 0xe1, 0x7d, 0x41, 0x7d, 0xa1, 0x7e, 0x01, 0x7e, 0x62, 0x7e, 0xc2, + 0x7f, 0x23, 0x7f, 0x84, 0x7f, 0xe5, 0x80, 0x47, 0x80, 0xa8, 0x81, 0x0a, + 0x81, 0x6b, 0x81, 0xcd, 0x82, 0x30, 0x82, 0x92, 0x82, 0xf4, 0x83, 0x57, + 0x83, 0xba, 0x84, 0x1d, 0x84, 0x80, 0x84, 0xe3, 0x85, 0x47, 0x85, 0xab, + 0x86, 0x0e, 0x86, 0x72, 0x86, 0xd7, 0x87, 0x3b, 0x87, 0x9f, 0x88, 0x04, + 0x88, 0x69, 0x88, 0xce, 0x89, 0x33, 0x89, 0x99, 0x89, 0xfe, 0x8a, 0x64, + 0x8a, 0xca, 0x8b, 0x30, 0x8b, 0x96, 0x8b, 0xfc, 0x8c, 0x63, 0x8c, 0xca, + 0x8d, 0x31, 0x8d, 0x98, 0x8d, 0xff, 0x8e, 0x66, 0x8e, 0xce, 0x8f, 0x36, + 0x8f, 0x9e, 0x90, 0x06, 0x90, 0x6e, 0x90, 0xd6, 0x91, 0x3f, 0x91, 0xa8, + 0x92, 0x11, 0x92, 0x7a, 0x92, 0xe3, 0x93, 0x4d, 0x93, 0xb6, 0x94, 0x20, + 0x94, 0x8a, 0x94, 0xf4, 0x95, 0x5f, 0x95, 0xc9, 0x96, 0x34, 0x96, 0x9f, + 0x97, 0x0a, 0x97, 0x75, 0x97, 0xe0, 0x98, 0x4c, 0x98, 0xb8, 0x99, 0x24, + 0x99, 0x90, 0x99, 0xfc, 0x9a, 0x68, 0x9a, 0xd5, 0x9b, 0x42, 0x9b, 0xaf, + 0x9c, 0x1c, 0x9c, 0x89, 0x9c, 0xf7, 0x9d, 0x64, 0x9d, 0xd2, 0x9e, 0x40, + 0x9e, 0xae, 0x9f, 0x1d, 0x9f, 0x8b, 0x9f, 0xfa, 0xa0, 0x69, 0xa0, 0xd8, + 0xa1, 0x47, 0xa1, 0xb6, 0xa2, 0x26, 0xa2, 0x96, 0xa3, 0x06, 0xa3, 0x76, + 0xa3, 0xe6, 0xa4, 0x56, 0xa4, 0xc7, 0xa5, 0x38, 0xa5, 0xa9, 0xa6, 0x1a, + 0xa6, 0x8b, 0xa6, 0xfd, 0xa7, 0x6e, 0xa7, 0xe0, 0xa8, 0x52, 0xa8, 0xc4, + 0xa9, 0x37, 0xa9, 0xa9, 0xaa, 0x1c, 0xaa, 0x8f, 0xab, 0x02, 0xab, 0x75, + 0xab, 0xe9, 0xac, 0x5c, 0xac, 0xd0, 0xad, 0x44, 0xad, 0xb8, 0xae, 0x2d, + 0xae, 0xa1, 0xaf, 0x16, 0xaf, 0x8b, 0xb0, 0x00, 0xb0, 0x75, 0xb0, 0xea, + 0xb1, 0x60, 0xb1, 0xd6, 0xb2, 0x4b, 0xb2, 0xc2, 0xb3, 0x38, 0xb3, 0xae, + 0xb4, 0x25, 0xb4, 0x9c, 0xb5, 0x13, 0xb5, 0x8a, 0xb6, 0x01, 0xb6, 0x79, + 0xb6, 0xf0, 0xb7, 0x68, 0xb7, 0xe0, 0xb8, 0x59, 0xb8, 0xd1, 0xb9, 0x4a, + 0xb9, 0xc2, 0xba, 0x3b, 0xba, 0xb5, 0xbb, 0x2e, 0xbb, 0xa7, 0xbc, 0x21, + 0xbc, 0x9b, 0xbd, 0x15, 0xbd, 0x8f, 0xbe, 0x0a, 0xbe, 0x84, 0xbe, 0xff, + 0xbf, 0x7a, 0xbf, 0xf5, 0xc0, 0x70, 0xc0, 0xec, 0xc1, 0x67, 0xc1, 0xe3, + 0xc2, 0x5f, 0xc2, 0xdb, 0xc3, 0x58, 0xc3, 0xd4, 0xc4, 0x51, 0xc4, 0xce, + 0xc5, 0x4b, 0xc5, 0xc8, 0xc6, 0x46, 0xc6, 0xc3, 0xc7, 0x41, 0xc7, 0xbf, + 0xc8, 0x3d, 0xc8, 0xbc, 0xc9, 0x3a, 0xc9, 0xb9, 0xca, 0x38, 0xca, 0xb7, + 0xcb, 0x36, 0xcb, 0xb6, 0xcc, 0x35, 0xcc, 0xb5, 0xcd, 0x35, 0xcd, 0xb5, + 0xce, 0x36, 0xce, 0xb6, 0xcf, 0x37, 0xcf, 0xb8, 0xd0, 0x39, 0xd0, 0xba, + 0xd1, 0x3c, 0xd1, 0xbe, 0xd2, 0x3f, 0xd2, 0xc1, 0xd3, 0x44, 0xd3, 0xc6, + 0xd4, 0x49, 0xd4, 0xcb, 0xd5, 0x4e, 0xd5, 0xd1, 0xd6, 0x55, 0xd6, 0xd8, + 0xd7, 0x5c, 0xd7, 0xe0, 0xd8, 0x64, 0xd8, 0xe8, 0xd9, 0x6c, 0xd9, 0xf1, + 0xda, 0x76, 0xda, 0xfb, 0xdb, 0x80, 0xdc, 0x05, 0xdc, 0x8a, 0xdd, 0x10, + 0xdd, 0x96, 0xde, 0x1c, 0xde, 0xa2, 0xdf, 0x29, 0xdf, 0xaf, 0xe0, 0x36, + 0xe0, 0xbd, 0xe1, 0x44, 0xe1, 0xcc, 0xe2, 0x53, 0xe2, 0xdb, 0xe3, 0x63, + 0xe3, 0xeb, 0xe4, 0x73, 0xe4, 0xfc, 0xe5, 0x84, 0xe6, 0x0d, 0xe6, 0x96, + 0xe7, 0x1f, 0xe7, 0xa9, 0xe8, 0x32, 0xe8, 0xbc, 0xe9, 0x46, 0xe9, 0xd0, + 0xea, 0x5b, 0xea, 0xe5, 0xeb, 0x70, 0xeb, 0xfb, 0xec, 0x86, 0xed, 0x11, + 0xed, 0x9c, 0xee, 0x28, 0xee, 0xb4, 0xef, 0x40, 0xef, 0xcc, 0xf0, 0x58, + 0xf0, 0xe5, 0xf1, 0x72, 0xf1, 0xff, 0xf2, 0x8c, 0xf3, 0x19, 0xf3, 0xa7, + 0xf4, 0x34, 0xf4, 0xc2, 0xf5, 0x50, 0xf5, 0xde, 0xf6, 0x6d, 0xf6, 0xfb, + 0xf7, 0x8a, 0xf8, 0x19, 0xf8, 0xa8, 0xf9, 0x38, 0xf9, 0xc7, 0xfa, 0x57, + 0xfa, 0xe7, 0xfb, 0x77, 0xfc, 0x07, 0xfc, 0x98, 0xfd, 0x29, 0xfd, 0xba, + 0xfe, 0x4b, 0xfe, 0xdc, 0xff, 0x6d, 0xff, 0xff, 0x58, 0x59, 0x5a, 0x20, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf3, 0x51, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x01, 0x16, 0xcc, 0x74, 0x65, 0x78, 0x74, 0x00, 0x00, 0x00, 0x00, + 0x43, 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x41, 0x70, + 0x70, 0x6c, 0x65, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x2c, 0x20, 0x32, 0x30, + 0x30, 0x38, 0x00, 0x00, 0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x1f, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x20, + 0x47, 0x72, 0x61, 0x79, 0x20, 0x47, 0x61, 0x6d, 0x6d, 0x61, 0x20, 0x32, + 0x2e, 0x32, 0x20, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6d, 0x6c, 0x75, 0x63, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x0c, + 0x65, 0x6e, 0x55, 0x53, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0xdc, + 0x65, 0x73, 0x45, 0x53, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x01, 0x18, + 0x64, 0x61, 0x44, 0x4b, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x02, 0x2a, + 0x64, 0x65, 0x44, 0x45, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x01, 0xde, + 0x66, 0x69, 0x46, 0x49, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x02, 0xa0, + 0x66, 0x72, 0x46, 0x55, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x02, 0x62, + 0x69, 0x74, 0x49, 0x54, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x03, 0x70, + 0x6e, 0x6c, 0x4e, 0x4c, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x01, 0x64, + 0x6e, 0x62, 0x4e, 0x4f, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x03, 0xc4, + 0x70, 0x74, 0x42, 0x52, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x03, 0x26, + 0x73, 0x76, 0x53, 0x45, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x02, 0x2a, + 0x6a, 0x61, 0x4a, 0x50, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x03, 0xfe, + 0x6b, 0x6f, 0x4b, 0x52, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x04, 0x24, + 0x7a, 0x68, 0x54, 0x57, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x04, 0x46, + 0x7a, 0x68, 0x43, 0x4e, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x04, 0x64, + 0x72, 0x75, 0x52, 0x55, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x01, 0xa4, + 0x70, 0x6c, 0x50, 0x4c, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x02, 0xe6, + 0x00, 0x47, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x72, 0x00, 0x69, + 0x00, 0x63, 0x00, 0x20, 0x00, 0x47, 0x00, 0x72, 0x00, 0x61, 0x00, 0x79, + 0x00, 0x20, 0x00, 0x47, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x61, + 0x00, 0x20, 0x00, 0x32, 0x00, 0x2e, 0x00, 0x32, 0x00, 0x20, 0x00, 0x50, + 0x00, 0x72, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x65, + 0x00, 0x50, 0x00, 0x65, 0x00, 0x72, 0x00, 0x66, 0x00, 0x69, 0x00, 0x6c, + 0x00, 0x20, 0x00, 0x67, 0x00, 0x65, 0x00, 0x6e, 0x00, 0xe9, 0x00, 0x72, + 0x00, 0x69, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x20, 0x00, 0x64, 0x00, 0x65, + 0x00, 0x20, 0x00, 0x67, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x61, + 0x00, 0x20, 0x00, 0x64, 0x00, 0x65, 0x00, 0x20, 0x00, 0x67, 0x00, 0x72, + 0x00, 0x69, 0x00, 0x73, 0x00, 0x65, 0x00, 0x73, 0x00, 0x20, 0x00, 0x32, + 0x00, 0x2c, 0x00, 0x32, 0x00, 0x41, 0x00, 0x6c, 0x00, 0x67, 0x00, 0x65, + 0x00, 0x6d, 0x00, 0x65, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x20, 0x00, 0x67, + 0x00, 0x72, 0x00, 0x69, 0x00, 0x6a, 0x00, 0x73, 0x00, 0x20, 0x00, 0x67, + 0x00, 0x61, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x20, 0x00, 0x32, + 0x00, 0x2c, 0x00, 0x32, 0x00, 0x2d, 0x00, 0x70, 0x00, 0x72, 0x00, 0x6f, + 0x00, 0x66, 0x00, 0x69, 0x00, 0x65, 0x00, 0x6c, 0x04, 0x1e, 0x04, 0x31, + 0x04, 0x49, 0x04, 0x30, 0x04, 0x4f, 0x00, 0x20, 0x04, 0x41, 0x04, 0x35, + 0x04, 0x40, 0x04, 0x30, 0x04, 0x4f, 0x00, 0x20, 0x04, 0x33, 0x04, 0x30, + 0x04, 0x3c, 0x04, 0x3c, 0x04, 0x30, 0x00, 0x20, 0x00, 0x32, 0x00, 0x2c, + 0x00, 0x32, 0x00, 0x2d, 0x04, 0x3f, 0x04, 0x40, 0x04, 0x3e, 0x04, 0x44, + 0x04, 0x38, 0x04, 0x3b, 0x04, 0x4c, 0x00, 0x41, 0x00, 0x6c, 0x00, 0x6c, + 0x00, 0x67, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x69, 0x00, 0x6e, + 0x00, 0x65, 0x00, 0x73, 0x00, 0x20, 0x00, 0x47, 0x00, 0x72, 0x00, 0x61, + 0x00, 0x75, 0x00, 0x73, 0x00, 0x74, 0x00, 0x75, 0x00, 0x66, 0x00, 0x65, + 0x00, 0x6e, 0x00, 0x70, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x69, + 0x00, 0x6c, 0x00, 0x20, 0x00, 0x47, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x6d, + 0x00, 0x61, 0x00, 0x20, 0x00, 0x32, 0x00, 0x2c, 0x00, 0x32, 0x00, 0x47, + 0x00, 0x65, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x72, 0x00, 0x69, 0x00, 0x73, + 0x00, 0x6b, 0x00, 0x20, 0x00, 0x67, 0x00, 0x72, 0x00, 0xe5, 0x00, 0x20, + 0x00, 0x32, 0x00, 0x2c, 0x00, 0x32, 0x00, 0x20, 0x00, 0x67, 0x00, 0x61, + 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x70, 0x00, 0x72, 0x00, 0x6f, + 0x00, 0x66, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x50, 0x00, 0x72, 0x00, 0x6f, + 0x00, 0x66, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x20, 0x00, 0x67, 0x00, 0xe9, + 0x00, 0x6e, 0x00, 0xe9, 0x00, 0x72, 0x00, 0x69, 0x00, 0x71, 0x00, 0x75, + 0x00, 0x65, 0x00, 0x20, 0x00, 0x67, 0x00, 0x72, 0x00, 0x69, 0x00, 0x73, + 0x00, 0x20, 0x00, 0x67, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x61, + 0x00, 0x20, 0x00, 0x32, 0x00, 0x2c, 0x00, 0x32, 0x00, 0x59, 0x00, 0x6c, + 0x00, 0x65, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x20, + 0x00, 0x68, 0x00, 0x61, 0x00, 0x72, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x61, + 0x00, 0x6e, 0x00, 0x20, 0x00, 0x67, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x6d, + 0x00, 0x61, 0x00, 0x20, 0x00, 0x32, 0x00, 0x2c, 0x00, 0x32, 0x00, 0x20, + 0x00, 0x2d, 0x00, 0x70, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x69, + 0x00, 0x69, 0x00, 0x6c, 0x00, 0x69, 0x00, 0x4f, 0x00, 0x67, 0x00, 0xf3, + 0x00, 0x6c, 0x00, 0x6e, 0x00, 0x79, 0x00, 0x20, 0x00, 0x70, 0x00, 0x72, + 0x00, 0x6f, 0x00, 0x66, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x20, 0x00, 0x73, + 0x00, 0x7a, 0x00, 0x61, 0x00, 0x72, 0x00, 0x6f, 0x01, 0x5b, 0x00, 0x63, + 0x00, 0x69, 0x00, 0x20, 0x00, 0x67, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x6d, + 0x00, 0x61, 0x00, 0x20, 0x00, 0x32, 0x00, 0x2c, 0x00, 0x32, 0x00, 0x50, + 0x00, 0x65, 0x00, 0x72, 0x00, 0x66, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x20, + 0x00, 0x47, 0x00, 0x65, 0x00, 0x6e, 0x00, 0xe9, 0x00, 0x72, 0x00, 0x69, + 0x00, 0x63, 0x00, 0x6f, 0x00, 0x20, 0x00, 0x64, 0x00, 0x61, 0x00, 0x20, + 0x00, 0x47, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x20, 0x00, 0x64, + 0x00, 0x65, 0x00, 0x20, 0x00, 0x43, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x7a, + 0x00, 0x61, 0x00, 0x73, 0x00, 0x20, 0x00, 0x32, 0x00, 0x2c, 0x00, 0x32, + 0x00, 0x50, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x69, 0x00, 0x6c, + 0x00, 0x6f, 0x00, 0x20, 0x00, 0x67, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x65, + 0x00, 0x72, 0x00, 0x69, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x20, 0x00, 0x64, + 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x20, 0x00, 0x67, + 0x00, 0x61, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x20, 0x00, 0x64, + 0x00, 0x65, 0x00, 0x69, 0x00, 0x20, 0x00, 0x67, 0x00, 0x72, 0x00, 0x69, + 0x00, 0x67, 0x00, 0x69, 0x00, 0x20, 0x00, 0x32, 0x00, 0x2c, 0x00, 0x32, + 0x00, 0x47, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x72, 0x00, 0x69, + 0x00, 0x73, 0x00, 0x6b, 0x00, 0x20, 0x00, 0x67, 0x00, 0x72, 0x00, 0xe5, + 0x00, 0x20, 0x00, 0x67, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x61, + 0x00, 0x20, 0x00, 0x32, 0x00, 0x2c, 0x00, 0x32, 0x00, 0x2d, 0x00, 0x70, + 0x00, 0x72, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x69, 0x00, 0x6c, 0x4e, 0x00, + 0x82, 0x2c, 0x30, 0xb0, 0x30, 0xec, 0x30, 0xa4, 0x30, 0xac, 0x30, 0xf3, + 0x30, 0xde, 0x00, 0x20, 0x00, 0x32, 0x00, 0x2e, 0x00, 0x32, 0x00, 0x20, + 0x30, 0xd7, 0x30, 0xed, 0x30, 0xd5, 0x30, 0xa1, 0x30, 0xa4, 0x30, 0xeb, + 0xc7, 0x7c, 0xbc, 0x18, 0x00, 0x20, 0xd6, 0x8c, 0xc0, 0xc9, 0x00, 0x20, + 0xac, 0x10, 0xb9, 0xc8, 0x00, 0x20, 0x00, 0x32, 0x00, 0x2e, 0x00, 0x32, + 0x00, 0x20, 0xd5, 0x04, 0xb8, 0x5c, 0xd3, 0x0c, 0xc7, 0x7c, 0x90, 0x1a, + 0x75, 0x28, 0x70, 0x70, 0x96, 0x8e, 0x51, 0x49, 0x5e, 0xa6, 0x00, 0x20, + 0x00, 0x32, 0x00, 0x2e, 0x00, 0x32, 0x00, 0x20, 0x82, 0x72, 0x5f, 0x69, + 0x63, 0xcf, 0x8f, 0xf0, 0x90, 0x1a, 0x75, 0x28, 0x70, 0x70, 0x5e, 0xa6, + 0x7c, 0xfb, 0x65, 0x70, 0x00, 0x20, 0x00, 0x32, 0x00, 0x2e, 0x00, 0x32, + 0x00, 0x20, 0x63, 0xcf, 0x8f, 0xf0, 0x65, 0x87, 0x4e, 0xf6, 0x00, 0x00, + } + + temporaryDirectory = temporaryDirectoryOrPanic() + SRGBV2MicroICCProfilePath = filepath.Join(temporaryDirectory, "srgb_v2_micro.icc") + SGrayV2MicroICCProfilePath = filepath.Join(temporaryDirectory, "sgray_v2_micro.icc") + SRGBIEC6196621ICCProfilePath = filepath.Join(temporaryDirectory, "srgb_iec61966_2_1.icc") + GenericGrayGamma22ICCProfilePath = filepath.Join(temporaryDirectory, "generic_gray_gamma_2_2.icc") +) + +func initializeICCProfiles() { + storeIccProfile(SRGBV2MicroICCProfilePath, sRGBV2MicroICCProfile) + storeIccProfile(SGrayV2MicroICCProfilePath, sGrayV2MicroICCProfile) + storeIccProfile(SRGBIEC6196621ICCProfilePath, sRGBIEC6196621ICCProfile) + storeIccProfile(GenericGrayGamma22ICCProfilePath, genericGrayGamma22ICCProfile) +} + +func storeIccProfile(path string, data []byte) { + err := os.WriteFile(path, data, 0600) + if err != nil { + panic(fmt.Sprintf("Couldn't store temporary file for ICC profile in '%v': %v", path, err.Error())) + } +} + +func temporaryDirectoryOrPanic() string { + temporaryDirectory, err := os.MkdirTemp("", "govips-") + if err != nil { + panic(fmt.Sprintf("Couldn't create temporary directory: %v", err.Error())) + } + + return temporaryDirectory +} diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/image.c b/vendor/github.com/davidbyttow/govips/v2/vips/image.c new file mode 100644 index 00000000000..6352f0d1843 --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/image.c @@ -0,0 +1,9 @@ +#include "image.h" + +int has_alpha_channel(VipsImage *image) { return vips_image_hasalpha(image); } + +void clear_image(VipsImage **image) { + // Reference-counting in libvips: https://www.libvips.org/API/current/using-from-c.html#using-C-ref + // https://docs.gtk.org/gobject/method.Object.unref.html + if (G_IS_OBJECT(*image)) g_object_unref(*image); +} diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/image.go b/vendor/github.com/davidbyttow/govips/v2/vips/image.go new file mode 100644 index 00000000000..82d5fac261d --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/image.go @@ -0,0 +1,2162 @@ +package vips + +// #include "image.h" +import "C" + +import ( + "bytes" + "errors" + "fmt" + "image" + "io" + "os" + "runtime" + "strconv" + "strings" + "sync" + "unsafe" +) + +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 + HeifThumbnail BoolParameter + SvgUnlimited BoolParameter +} + +// 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.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())) + } + 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 // TODO: Use this param during save +} + +// 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 +} + +// NewTiffExportParams creates default values for an export of a TIFF image. +func NewTiffExportParams() *TiffExportParams { + return &TiffExportParams{ + Quality: 80, + Compression: TiffCompressionLzw, + Predictor: TiffPredictorHorizontal, + } +} + +// GifExportParams are options when exporting a GIF to file or buffer +// Please note that if vips version is above 8.12, then `vips_gifsave_buffer` is used, and only `Dither`, `Effort`, `Bitdepth` is used. +// If vips version is below 8.12, then `vips_magicksave_buffer` is used, and only `Bitdepth`, `Quality` is used. +// StripMetadata does nothing to Gif images. +type GifExportParams struct { + StripMetadata bool + 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, + } +} + +// 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) { + startupIfNeeded() + + 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) { + startupIfNeeded() + + 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) { + startupIfNeeded() + + 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) { + out, err := vipsCopyImage(r.image) + 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) { + out, err := vipsCopyImageChangingResolution(r.image, xres, 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) { + out, err := vipsCopyImageChangingInterpretation(r.image, 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 := vipsXYZ(width, height) + 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 := vipsIdentity(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 := vipsBlack(width, height) + imageRef := &ImageRef{ + image: vipsImage, + } + runtime.SetFinalizer(imageRef, finalizeImage) + return imageRef, err +} + +func newImageRef(vipsImage *C.VipsImage, currentFormat ImageType, originalFormat ImageType, buf []byte) *ImageRef { + imageRef := &ImageRef{ + image: vipsImage, + format: currentFormat, + originalFormat: originalFormat, + buf: buf, + } + 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 + } + + r.buf = nil + + r.lock.Unlock() +} + +// 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 { + 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 { + return vipsHasIPTC(r.image) +} + +// HasAlpha returns if the image has an alpha layer. +func (r *ImageRef) HasAlpha() bool { + return vipsHasAlpha(r.image) +} + +// Orientation returns the orientation number as it appears in the EXIF, if present +func (r *ImageRef) Orientation() int { + 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 { + out, err := vipsCopyImage(r.image) + 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 { + out, err := vipsCopyImage(r.image) + 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 { + 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 + } + + 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 { + out, err := vipsCopyImage(r.image) + 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 { + return vipsGetPageHeight(r.image) +} + +// GetPageHeight return the height of a single page +// Deprecated use PageHeight() instead +func (r *ImageRef) GetPageHeight() int { + 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 { + out, err := vipsCopyImage(r.image) + 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) { + 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 { + var data []C.int + for _, d := range delay { + data = append(data, C.int(d)) + } + return vipsImageSetDelay(r.image, data) +} + +// 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + if params == nil { + params = NewJxlExportParams() + } + + buf, err := vipsSaveJxlToBuffer(r.image, *params) + if err != nil { + return nil, nil, err + } + + return buf, r.newMetadata(ImageTypeJXL), nil +} + +// CompositeMulti composites the given overlay image on top of the associated image with provided blending mode. +func (r *ImageRef) CompositeMulti(ins []*ImageComposite) error { + 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 { + out, err := vipsComposite2(r.image, overlay.image, mode, x, 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 { + out, err := vipsInsert(r.image, sub.image, x, y, expand, background) + 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 { + 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 { + allImages := append([]*ImageRef{r}, images...) + inputs := make([]*C.VipsImage, len(allImages)) + for i := range inputs { + inputs[i] = allImages[i].image + } + out, err := vipsArrayJoin(inputs, across) + 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 { + out, err := vipsMapim(r.image, index.image) + 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 { + out, err := vipsMaplut(r.image, lut.image) + 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 { + out, err := vipsExtractBand(r.image, band, 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) { + out, err := vipsExtractBand(r.image, band, 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 { + vipsImages := []*C.VipsImage{r.image} + for _, vipsImage := range images { + vipsImages = append(vipsImages, vipsImage.image) + } + + out, err := vipsBandJoin(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) { + var out []*ImageRef + for i := 0; i < r.Bands(); i++ { + img, err := vipsExtractBand(r.image, i, 1) + 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 { + out, err := vipsBandJoinConst(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 { + 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 { + if r.preMultiplication != nil || !vipsHasAlpha(r.image) { + return nil + } + + band := r.BandFormat() + + out, err := vipsPremultiplyAlpha(r.image) + 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 { + if r.preMultiplication == nil { + return nil + } + + unpremultiplied, err := vipsUnpremultiplyAlpha(r.image) + if err != nil { + return err + } + defer clearImage(unpremultiplied) + + out, err := vipsCast(unpremultiplied, r.preMultiplication.bandFormat) + if err != nil { + return err + } + + r.preMultiplication = nil + r.setImage(out) + return nil +} + +// Cast converts the image to a target band format +func (r *ImageRef) Cast(format BandFormat) error { + out, err := vipsCast(r.image, format) + if err != nil { + return err + } + 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 { + out, err := vipsAdd(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 { + out, err := vipsMultiply(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 { + out, err := vipsDivide(r.image, denominator.image) + 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 { + if len(a) != len(b) { + return errors.New("a and b must be of same length") + } + + out, err := vipsLinear(r.image, a, b, len(a)) + 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 { + out, err := vipsLinear1(r.image, a, b) + 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 { + out, err := vipsGamma(r.image, gamma) + if err != nil { + return err + } + r.setImage(out) + return nil +} + +// 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 { + out, err := vipsAutoRotate(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 { + 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 +} + +// GetICCProfile retrieves the ICC profile data (if any) from the image. +func (r *ImageRef) GetICCProfile() []byte { + 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 := vipsCopyImage(r.image) + 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 { + 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 + } + + 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 := vipsCopyImage(r.image) + 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 { + return vipsImageGetFields(r.image) +} + +func (r *ImageRef) SetBlob(name string, data []byte) { + vipsImageSetBlob(r.image, name, data) +} + +func (r *ImageRef) GetBlob(name string) []byte { + return vipsImageGetBlob(r.image, name) +} + +func (r *ImageRef) SetDouble(name string, f float64) { + vipsImageSetDouble(r.image, name, f) +} + +func (r *ImageRef) GetDouble(name string) float64 { + return vipsImageGetDouble(r.image, name) +} + +func (r *ImageRef) SetInt(name string, i int) { + vipsImageSetInt(r.image, name, i) +} + +func (r *ImageRef) GetInt(name string) int { + return vipsImageGetInt(r.image, name) +} + +func (r *ImageRef) SetString(name string, str string) { + vipsImageSetString(r.image, name, str) +} + +func (r *ImageRef) GetString(name string) string { + return vipsImageGetString(r.image, name) +} + +func (r *ImageRef) GetAsString(name string) string { + 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 { + 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 { + out, err := vipsFlatten(r.image, backgroundColor) + 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 := vipsGaussianBlur(r.image, sigma, 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 := vipsSharpen(r.image, sigma, x1, 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 := vipsSobel(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 := vipsInvert(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) { + out, err := vipsAverage(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) { + 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) { + 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 := vipsStats(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 := vipsHistFind(r.image) + if err != nil { + return err + } + r.setImage(out) + return nil +} + +// HistogramCumulative form cumulative histogram. +func (r *ImageRef) HistogramCumulative() error { + out, err := vipsHistCum(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 := vipsHistNorm(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) { + return vipsHistEntropy(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 { + err := vipsDrawRect(r.image, ink, left, top, width, height, fill) + if err != nil { + return err + } + 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 { + out, err := vipsRank(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(float64(pageHeight)*scale + 0.5) + 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 := vipsZoom(r.image, xFactor, yFactor) + 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 + var matrixValues []float64 + 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) + + // 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 := vipsRecomb(r.image, matrixImage) + 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 := vipsRotate(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 := vipsGrid(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 := vipsReplicate(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) { + 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 { + 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 +} + +// 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(), + } +} + +// 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.h b/vendor/github.com/davidbyttow/govips/v2/vips/image.h new file mode 100644 index 00000000000..0a116f00820 --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/image.h @@ -0,0 +1,8 @@ +// https://libvips.github.io/libvips/API/current/VipsImage.html + +#include +#include + +int has_alpha_channel(VipsImage *image); + +void clear_image(VipsImage **image); diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/label.c b/vendor/github.com/davidbyttow/govips/v2/vips/label.c new file mode 100644 index 00000000000..01de8134389 --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/label.c @@ -0,0 +1,37 @@ +#include "label.h" + +int text(VipsImage **out, const char *text, const char *font, int width, + int height, VipsAlign align, int dpi) { + return vips_text(out, text, "font", font, "width", width, "height", height, + "align", align, "dpi", dpi, NULL); +} + +int label(VipsImage *in, VipsImage **out, LabelOptions *o) { + double ones[3] = {1, 1, 1}; + VipsImage *base = vips_image_new(); + VipsImage **t = (VipsImage **)vips_object_local_array(VIPS_OBJECT(base), 9); + if (vips_text(&t[0], o->Text, "font", o->Font, "width", o->Width, "height", + o->Height, "align", o->Align, NULL) || + vips_linear1(t[0], &t[1], o->Opacity, 0.0, NULL) || + vips_cast(t[1], &t[2], VIPS_FORMAT_UCHAR, NULL) || + 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; + } + if (vips_black(&t[4], 1, 1, NULL) || + vips_linear(t[4], &t[5], ones, o->Color, 3, NULL) || + vips_cast(t[5], &t[6], VIPS_FORMAT_UCHAR, NULL) || + vips_copy(t[6], &t[7], "interpretation", in->Type, NULL) || + vips_embed(t[7], &t[8], 0, 0, in->Xsize, in->Ysize, "extend", + VIPS_EXTEND_COPY, NULL)) { + g_object_unref(base); + return 1; + } + if (vips_ifthenelse(t[3], t[8], in, out, "blend", TRUE, NULL)) { + g_object_unref(base); + return 1; + } + g_object_unref(base); + return 0; +} diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/label.go b/vendor/github.com/davidbyttow/govips/v2/vips/label.go new file mode 100644 index 00000000000..84320b63400 --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/label.go @@ -0,0 +1,84 @@ +package vips + +// #include "label.h" +import "C" +import "unsafe" + +// Align represents VIPS_ALIGN +type Align int + +// Direction enum +const ( + AlignLow Align = C.VIPS_ALIGN_LOW + AlignCenter Align = C.VIPS_ALIGN_CENTRE + AlignHigh Align = C.VIPS_ALIGN_HIGH +) + +// DefaultFont is the default font to be used for label texts created by govips +const DefaultFont = "sans 10" + +// LabelParams represents a text-based label +type LabelParams struct { + Text string + Font string + Width Scalar + Height Scalar + OffsetX Scalar + OffsetY Scalar + Opacity float32 + Color Color + Alignment Align +} + +type vipsLabelOptions struct { + Text *C.char + Font *C.char + Width C.int + Height C.int + OffsetX C.int + OffsetY C.int + Alignment C.VipsAlign + DPI C.int + Margin C.int + Opacity C.float + Color [3]C.double +} + +func labelImage(in *C.VipsImage, params *LabelParams) (*C.VipsImage, error) { + incOpCounter("label") + var out *C.VipsImage + + text := C.CString(params.Text) + defer freeCString(text) + + font := C.CString(params.Font) + defer freeCString(font) + + // todo: release color? + color := [3]C.double{C.double(params.Color.R), C.double(params.Color.G), C.double(params.Color.B)} + + w := params.Width.GetRounded(int(in.Xsize)) + h := params.Height.GetRounded(int(in.Ysize)) + offsetX := params.OffsetX.GetRounded(int(in.Xsize)) + offsetY := params.OffsetY.GetRounded(int(in.Ysize)) + + opts := vipsLabelOptions{ + Text: text, + Font: font, + Width: C.int(w), + Height: C.int(h), + OffsetX: C.int(offsetX), + OffsetY: C.int(offsetY), + Alignment: C.VipsAlign(params.Alignment), + Opacity: C.float(params.Opacity), + Color: color, + } + + // todo: release inline pointer? + err := C.label(in, &out, (*C.LabelOptions)(unsafe.Pointer(&opts))) + if err != 0 { + return nil, handleImageError(out) + } + + return out, nil +} diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/label.h b/vendor/github.com/davidbyttow/govips/v2/vips/label.h new file mode 100644 index 00000000000..332a5da30f1 --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/label.h @@ -0,0 +1,21 @@ +#include +#include + +typedef struct { + const char *Text; + const char *Font; + int Width; + int Height; + int OffsetX; + int OffsetY; + VipsAlign Align; + int DPI; + int Margin; + float Opacity; + double Color[3]; +} LabelOptions; + +int label(VipsImage *in, VipsImage **out, LabelOptions *o); + +int text(VipsImage **out, const char *text, const char *font, int width, + int height, VipsAlign align, int dpi); diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/lang.go b/vendor/github.com/davidbyttow/govips/v2/vips/lang.go new file mode 100644 index 00000000000..6889b798bfc --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/lang.go @@ -0,0 +1,49 @@ +package vips + +// #include +// #include +import "C" + +import ( + "reflect" + "unsafe" +) + +func freeCString(s *C.char) { + C.free(unsafe.Pointer(s)) +} + +func gFreePointer(ref unsafe.Pointer) { + C.g_free(C.gpointer(ref)) +} + +func boolToInt(b bool) int { + if b { + return 1 + } + return 0 +} + +func toGboolean(b bool) C.gboolean { + if b { + return C.gboolean(1) + } + return C.gboolean(0) +} + +func fromGboolean(b C.gboolean) bool { + return b != 0 +} + +func fromCArrayInt(out *C.int, n int) []int { + var result = make([]int, n) + var data []C.int + sh := (*reflect.SliceHeader)(unsafe.Pointer(&data)) + sh.Data = uintptr(unsafe.Pointer(out)) + sh.Len = n + sh.Cap = n + for i := range data { + result[i] = int(data[i]) + } + return result +} diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/lang.h b/vendor/github.com/davidbyttow/govips/v2/vips/lang.h new file mode 100644 index 00000000000..27c81384b62 --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/lang.h @@ -0,0 +1,4 @@ +#include +#include + +#define INT_TO_GBOOLEAN(bool) (bool > 0 ? TRUE : FALSE) diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/logging.go b/vendor/github.com/davidbyttow/govips/v2/vips/logging.go new file mode 100644 index 00000000000..5053d0162f2 --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/logging.go @@ -0,0 +1,98 @@ +package vips + +// #include +import "C" +import ( + "log" +) + +// LogLevel is the enum controlling logging message verbosity. +type LogLevel int + +// The logging verbosity levels classify and filter logging messages. +// From most to least verbose, they are debug, info, message, warning, critical and error. +const ( + LogLevelError LogLevel = C.G_LOG_LEVEL_ERROR + LogLevelCritical LogLevel = C.G_LOG_LEVEL_CRITICAL + LogLevelWarning LogLevel = C.G_LOG_LEVEL_WARNING + LogLevelMessage LogLevel = C.G_LOG_LEVEL_MESSAGE + LogLevelInfo LogLevel = C.G_LOG_LEVEL_INFO + LogLevelDebug LogLevel = C.G_LOG_LEVEL_DEBUG +) + +// Three global variables which keep state of the current logging handler +// function, desired verbosity for logging and whether defaults have been +// overridden. Set by LoggingSettings() +var ( + currentLoggingHandlerFunction LoggingHandlerFunction + currentLoggingVerbosity LogLevel + currentLoggingOverridden bool +) + +// govipsLoggingHandler is the private bridge function exported to the C library +// and called by glib and libvips for each logging message. It will call govipsLog +// which in turn will filter based on verbosity and direct the messages to the +// currently chosen LoggingHandlerFunction. +// +//export govipsLoggingHandler +func govipsLoggingHandler(messageDomain *C.char, messageLevel C.int, message *C.char) { + govipsLog(C.GoString(messageDomain), LogLevel(messageLevel), C.GoString(message)) +} + +// LoggingHandlerFunction is a function which will be called for each log message. +// By default, govips sends logging messages to os.Stderr. If you want to log elsewhere +// such as to a file or to a state variable which you inspect yourself, define a new +// logging handler function and set it via LoggingSettings(). +type LoggingHandlerFunction func(messageDomain string, messageLevel LogLevel, message string) + +// LoggingSettings sets the logging handler and logging verbosity for govips. +// The handler function is the function which will be called for each log message. +// You can define one yourself to log somewhere else besides the default (stderr). +// Use nil as handler to use standard logging handler. +// Verbosity is the minimum logLevel you want to log. Default is logLevelInfo +// due to backwards compatibility but it's quite verbose for a library. +// Suggest setting it to at least logLevelWarning. Use logLevelDebug for debugging. +func LoggingSettings(handler LoggingHandlerFunction, verbosity LogLevel) { + currentLoggingOverridden = true + govipsLoggingSettings(handler, verbosity) +} + +func govipsLoggingSettings(handler LoggingHandlerFunction, verbosity LogLevel) { + if handler == nil { + currentLoggingHandlerFunction = defaultLoggingHandlerFunction + } else { + currentLoggingHandlerFunction = handler + } + + currentLoggingVerbosity = verbosity + // TODO turn on debugging in libvips and redirect to handler when setting verbosity to debug + // This way debugging information would go to the same channel as all other logging +} + +func defaultLoggingHandlerFunction(messageDomain string, messageLevel LogLevel, message string) { + var messageLevelDescription string + switch messageLevel { + case LogLevelError: + messageLevelDescription = "error" + case LogLevelCritical: + messageLevelDescription = "critical" + case LogLevelWarning: + messageLevelDescription = "warning" + case LogLevelMessage: + messageLevelDescription = "message" + case LogLevelInfo: + messageLevelDescription = "info" + case LogLevelDebug: + messageLevelDescription = "debug" + } + + log.Printf("[%v.%v] %v", messageDomain, messageLevelDescription, message) +} + +// govipsLog is the default function used to log debug or error messages internally in govips. +// It's used by all govips functionality directly, as well as by glib and libvips via the C bridge. +func govipsLog(messageDomain string, messageLevel LogLevel, message string) { + if messageLevel <= currentLoggingVerbosity { + currentLoggingHandlerFunction(messageDomain, messageLevel, message) + } +} diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/math.go b/vendor/github.com/davidbyttow/govips/v2/vips/math.go new file mode 100644 index 00000000000..c7176f1f45c --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/math.go @@ -0,0 +1,57 @@ +package vips + +import "math" + +// Scalar is the basic scalar measurement of an image's height, width or offset coordinate. +type Scalar struct { + Value float64 + Relative bool +} + +// ValueOf takes a floating point value and returns a corresponding Scalar struct +func ValueOf(value float64) Scalar { + return Scalar{value, false} +} + +// IsZero checkes whether the associated Scalar's value is zero. +func (s *Scalar) IsZero() bool { + return s.Value == 0 && !s.Relative +} + +// SetInt sets an integer value for the associated Scalar. +func (s *Scalar) SetInt(value int) { + s.Set(float64(value)) +} + +// Set sets a float value for the associated Scalar. +func (s *Scalar) Set(value float64) { + s.Value = value + s.Relative = false +} + +// SetScale sets a float value for the associated Scalar and makes it relative. +func (s *Scalar) SetScale(f float64) { + s.Value = f + s.Relative = true +} + +// Get returns the value of the scalar. Either absolute, or if relative, multiplied by the base given as parameter. +func (s *Scalar) Get(base int) float64 { + if s.Relative { + return s.Value * float64(base) + } + return s.Value +} + +// GetRounded returns the value of the associated Scalar, rounded to the nearest integer, if absolute. +// If the Scalar is relative, it will be multiplied by the supplied base parameter. +func (s *Scalar) GetRounded(base int) int { + return roundFloat(s.Get(base)) +} + +func roundFloat(f float64) int { + if f < 0 { + return int(math.Ceil(f - 0.5)) + } + return int(math.Floor(f + 0.5)) +} diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/morphology.c b/vendor/github.com/davidbyttow/govips/v2/vips/morphology.c new file mode 100644 index 00000000000..b57fdd9192e --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/morphology.c @@ -0,0 +1,6 @@ +#include "morphology.h" + +int rank(VipsImage *in, VipsImage **out, int width, int height, int index) { + return vips_rank(in, out, width, height, index, NULL); +} + diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/morphology.go b/vendor/github.com/davidbyttow/govips/v2/vips/morphology.go new file mode 100644 index 00000000000..75e8668b9d7 --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/morphology.go @@ -0,0 +1,17 @@ +package vips + +// #include "morphology.h" +import "C" + +// https://libvips.github.io/libvips/API/current/libvips-morphology.html#vips-rank +func vipsRank(in *C.VipsImage, width int, height int, index int) (*C.VipsImage, error) { + incOpCounter("rank") + var out *C.VipsImage + + err := C.rank(in, &out, C.int(width), C.int(height), C.int(index)) + if int(err) != 0 { + return nil, handleImageError(out) + } + + return out, nil +} diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/morphology.h b/vendor/github.com/davidbyttow/govips/v2/vips/morphology.h new file mode 100644 index 00000000000..fe8e9e1a04e --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/morphology.h @@ -0,0 +1,6 @@ +// https://libvips.github.io/libvips/API/current/libvips-morphology.html + +#include +#include + +int rank(VipsImage *in, VipsImage **out, int width, int height, int index); diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/resample.c b/vendor/github.com/davidbyttow/govips/v2/vips/resample.c new file mode 100644 index 00000000000..aabc4102a94 --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/resample.c @@ -0,0 +1,61 @@ +#include "resample.h" + +int shrink_image(VipsImage *in, VipsImage **out, double xshrink, + double yshrink) { + return vips_shrink(in, out, xshrink, yshrink, NULL); +} + +int reduce_image(VipsImage *in, VipsImage **out, double xshrink, + double yshrink) { + return vips_reduce(in, out, xshrink, yshrink, NULL); +} + +int affine_image(VipsImage *in, VipsImage **out, double a, double b, double c, + double d, VipsInterpolate *interpolator) { + return vips_affine(in, out, a, b, c, d, "interpolate", interpolator, NULL); +} + +int resize_image(VipsImage *in, VipsImage **out, double scale, gdouble vscale, + int kernel) { + if (vscale > 0) { + return vips_resize(in, out, scale, "vscale", vscale, "kernel", kernel, + NULL); + } + + return vips_resize(in, out, scale, "kernel", kernel, NULL); +} + +int thumbnail(const char *filename, VipsImage **out, + int width, int height, int crop, int size) { + return vips_thumbnail(filename, out, width, "height", height, + "crop", crop, "size", size, NULL); +} + +int thumbnail_image(VipsImage *in, VipsImage **out, int width, int height, + int crop, int size) { + return vips_thumbnail_image(in, out, width, "height", height, "crop", crop, + "size", size, NULL); +} + +int thumbnail_buffer_with_option(void *buf, size_t len, VipsImage **out, + int width, int height, int crop, int size, + const char *option_string) { + return vips_thumbnail_buffer(buf, len, out, width, "height", height, + "crop", crop, "size", size, + "option_string", option_string, NULL); +} + +int thumbnail_buffer(void *buf, size_t len, VipsImage **out, + int width, int height, int crop, int size) { + return vips_thumbnail_buffer(buf, len, out, width, "height", height, + "crop", crop, "size", size, NULL); +} + +int mapim(VipsImage *in, VipsImage **out, VipsImage *index) { + return vips_mapim(in, out, index, NULL); +} + +int maplut(VipsImage *in, VipsImage **out, VipsImage *lut) { + return vips_maplut(in, out, lut, NULL); +} + diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/resample.go b/vendor/github.com/davidbyttow/govips/v2/vips/resample.go new file mode 100644 index 00000000000..9998d523273 --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/resample.go @@ -0,0 +1,146 @@ +package vips + +// #include "resample.h" +import "C" +import ( + "os" + "runtime" + "unsafe" +) + +// Kernel represents VipsKernel type +type Kernel int + +// Kernel enum +const ( + KernelAuto Kernel = -1 + KernelNearest Kernel = C.VIPS_KERNEL_NEAREST + KernelLinear Kernel = C.VIPS_KERNEL_LINEAR + KernelCubic Kernel = C.VIPS_KERNEL_CUBIC + KernelLanczos2 Kernel = C.VIPS_KERNEL_LANCZOS2 + KernelLanczos3 Kernel = C.VIPS_KERNEL_LANCZOS3 + KernelMitchell Kernel = C.VIPS_KERNEL_MITCHELL +) + +// Size represents VipsSize type +type Size int + +const ( + SizeBoth Size = C.VIPS_SIZE_BOTH + SizeUp Size = C.VIPS_SIZE_UP + SizeDown Size = C.VIPS_SIZE_DOWN + SizeForce Size = C.VIPS_SIZE_FORCE + SizeLast Size = C.VIPS_SIZE_LAST +) + +// https://libvips.github.io/libvips/API/current/libvips-resample.html#vips-resize +func vipsResizeWithVScale(in *C.VipsImage, hscale, vscale float64, kernel Kernel) (*C.VipsImage, error) { + incOpCounter("resize") + var out *C.VipsImage + + // libvips recommends Lanczos3 as the default kernel + if kernel == KernelAuto { + kernel = KernelLanczos3 + } + + if err := C.resize_image(in, &out, C.double(hscale), C.double(vscale), C.int(kernel)); err != 0 { + return nil, handleImageError(out) + } + + return out, nil +} + +func vipsThumbnail(in *C.VipsImage, width, height int, crop Interesting, size Size) (*C.VipsImage, error) { + incOpCounter("thumbnail") + var out *C.VipsImage + + if err := C.thumbnail_image(in, &out, C.int(width), C.int(height), C.int(crop), C.int(size)); err != 0 { + return nil, handleImageError(out) + } + + return out, nil +} + +// https://www.libvips.org/API/current/libvips-resample.html#vips-thumbnail +func vipsThumbnailFromFile(filename string, width, height int, crop Interesting, size Size, params *ImportParams) (*C.VipsImage, ImageType, error) { + var out *C.VipsImage + + filenameOption := filename + if params != nil { + filenameOption += "[" + params.OptionString() + "]" + } + + cFileName := C.CString(filenameOption) + defer freeCString(cFileName) + + if err := C.thumbnail(cFileName, &out, C.int(width), C.int(height), C.int(crop), C.int(size)); err != 0 { + err := handleImageError(out) + if src, err2 := os.ReadFile(filename); err2 == nil { + if isBMP(src) { + if src2, err3 := bmpToPNG(src); err3 == nil { + return vipsThumbnailFromBuffer(src2, width, height, crop, size, params) + } + } + } + return nil, ImageTypeUnknown, err + } + + imageType := vipsDetermineImageTypeFromMetaLoader(out) + return out, imageType, nil +} + +// https://www.libvips.org/API/current/libvips-resample.html#vips-thumbnail-buffer +func vipsThumbnailFromBuffer(buf []byte, width, height int, crop Interesting, size Size, params *ImportParams) (*C.VipsImage, ImageType, error) { + src := buf + // Reference src here so it's not garbage collected during image initialization. + defer runtime.KeepAlive(src) + + var out *C.VipsImage + + var err C.int + + if params == nil { + err = C.thumbnail_buffer(unsafe.Pointer(&src[0]), C.size_t(len(src)), &out, C.int(width), C.int(height), C.int(crop), C.int(size)) + } else { + cOptionString := C.CString(params.OptionString()) + defer freeCString(cOptionString) + + err = C.thumbnail_buffer_with_option(unsafe.Pointer(&src[0]), C.size_t(len(src)), &out, C.int(width), C.int(height), C.int(crop), C.int(size), cOptionString) + } + if err != 0 { + err := handleImageError(out) + if isBMP(src) { + if src2, err2 := bmpToPNG(src); err2 == nil { + return vipsThumbnailFromBuffer(src2, width, height, crop, size, params) + } + } + return nil, ImageTypeUnknown, err + } + + imageType := vipsDetermineImageTypeFromMetaLoader(out) + return out, imageType, nil +} + +// https://libvips.github.io/libvips/API/current/libvips-resample.html#vips-mapim +func vipsMapim(in *C.VipsImage, index *C.VipsImage) (*C.VipsImage, error) { + incOpCounter("mapim") + var out *C.VipsImage + + if err := C.mapim(in, &out, index); err != 0 { + return nil, handleImageError(out) + } + + return out, nil +} + +// https://libvips.github.io/libvips/API/current/libvips-histogram.html#vips-maplut +func vipsMaplut(in *C.VipsImage, lut *C.VipsImage) (*C.VipsImage, error) { + incOpCounter("maplut") + var out *C.VipsImage + + if err := C.maplut(in, &out, lut); err != 0 { + return nil, handleImageError(out) + } + + return out, nil +} diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/resample.h b/vendor/github.com/davidbyttow/govips/v2/vips/resample.h new file mode 100644 index 00000000000..9df5e132c9b --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/resample.h @@ -0,0 +1,24 @@ +// https://libvips.github.io/libvips/API/current/libvips-resample.html + +#include +#include + +int shrink_image(VipsImage *in, VipsImage **out, double xshrink, + double yshrink); +int reduce_image(VipsImage *in, VipsImage **out, double xshrink, + double yshrink); +int affine_image(VipsImage *in, VipsImage **out, double a, double b, double c, + double d, VipsInterpolate *interpolator); +int resize_image(VipsImage *in, VipsImage **out, double scale, gdouble vscale, + int kernel); +int thumbnail(const char *filename, VipsImage **out, int width, int height, + int crop, int size); +int thumbnail_image(VipsImage *in, VipsImage **out, int width, int height, + int crop, int size); +int thumbnail_buffer(void *buf, size_t len, VipsImage **out, int width, int height, + int crop, int size); +int thumbnail_buffer_with_option(void *buf, size_t len, VipsImage **out, + int width, int height, int crop, int size, + const char *option_string); +int mapim(VipsImage *in, VipsImage **out, VipsImage *index); +int maplut(VipsImage *in, VipsImage **out, VipsImage *lut); diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/stats.go b/vendor/github.com/davidbyttow/govips/v2/vips/stats.go new file mode 100644 index 00000000000..d3208c75cf4 --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/stats.go @@ -0,0 +1,56 @@ +package vips + +import "sync" + +// RuntimeStats is a data structure to house a map of govips operation counts +type RuntimeStats struct { + OperationCounts map[string]int64 +} + +var ( + operationCounter chan string + runtimeStats *RuntimeStats + statLock sync.RWMutex +) + +func incOpCounter(op string) { + if operationCounter != nil { + operationCounter <- op + } +} + +func collectStats() chan struct{} { + operationCounter = make(chan string, 100) + done := make(chan struct{}) + exit := false + go func() { + for !exit { + select { + case op := <-operationCounter: + statLock.Lock() + runtimeStats.OperationCounts[op] = runtimeStats.OperationCounts[op] + 1 + statLock.Unlock() + case <-done: + exit = true + break + } + } + }() + return done +} + +// ReadRuntimeStats returns operation counts for govips +func ReadRuntimeStats(stats *RuntimeStats) { + statLock.RLock() + defer statLock.RUnlock() + stats.OperationCounts = make(map[string]int64) + for k, v := range runtimeStats.OperationCounts { + stats.OperationCounts[k] = v + } +} + +func init() { + runtimeStats = &RuntimeStats{ + OperationCounts: make(map[string]int64), + } +} diff --git a/vendor/github.com/davidbyttow/govips/v2/vips/test_resources.go b/vendor/github.com/davidbyttow/govips/v2/vips/test_resources.go new file mode 100644 index 00000000000..b2e91e4cd34 --- /dev/null +++ b/vendor/github.com/davidbyttow/govips/v2/vips/test_resources.go @@ -0,0 +1,6 @@ +package vips + +// relative to "/vips/.." +const ( + resources = "../resources/" +) diff --git a/vendor/modules.txt b/vendor/modules.txt index 13eb87b66d7..76b654b6849 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -745,6 +745,9 @@ github.com/cyphar/filepath-securejoin # github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc ## explicit github.com/davecgh/go-spew/spew +# github.com/davidbyttow/govips/v2 v2.15.0 +## explicit; go 1.15 +github.com/davidbyttow/govips/v2/vips # github.com/deckarep/golang-set v1.8.0 ## explicit; go 1.17 github.com/deckarep/golang-set From 85aee62876db2ac2cac6b27e292ff6a46bcf3ac3 Mon Sep 17 00:00:00 2001 From: Ralf Haferkamp Date: Tue, 15 Oct 2024 17:44:28 +0200 Subject: [PATCH 04/11] Add ENABLE_VIPS flag to Makefile To build with libvips support use 'make -C ocis build ENABLE_VIPS=true' --- ocis/Makefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ocis/Makefile b/ocis/Makefile index 7a2fef06156..bcbe4b5c273 100644 --- a/ocis/Makefile +++ b/ocis/Makefile @@ -3,6 +3,10 @@ NAME := ocis TAGS := disable_crypt +ifdef ENABLE_VIPS + TAGS := ${TAGS},enable_vips +endif + include ../.make/recursion.mk ############ tooling ############ From 777fa9e888145546fce197e02052fe713137c983 Mon Sep 17 00:00:00 2001 From: Ralf Haferkamp Date: Tue, 15 Oct 2024 18:20:27 +0200 Subject: [PATCH 05/11] dev: Enable VIPS in local docker builds As libvips is not available as a static library we can no longer create a statically linked ocis binary for the docker images when libvips is enabled. We also need to make sure that the base images used for building ocis needs to match the image where ocis is installed when creating a shared binary. --- .make/release.mk | 30 ++++++++++++++---------- Dockerfile | 6 ++--- ocis/docker/Dockerfile.linux.amd64 | 4 ++-- ocis/docker/Dockerfile.linux.arm64 | 4 ++-- ocis/docker/Dockerfile.linux.debug.amd64 | 2 +- 5 files changed, 26 insertions(+), 20 deletions(-) diff --git a/.make/release.mk b/.make/release.mk index 394ac97f8ea..bbe7a1fdce4 100644 --- a/.make/release.mk +++ b/.make/release.mk @@ -13,14 +13,20 @@ release-dirs: DOCKER_LDFLAGS += -X "$(OCIS_REPO)/ocis-pkg/config/defaults.BaseDataPathType=path" -X "$(OCIS_REPO)/ocis-pkg/config/defaults.BaseDataPathValue=/var/lib/ocis" DOCKER_LDFLAGS += -X "$(OCIS_REPO)/ocis-pkg/config/defaults.BaseConfigPathType=path" -X "$(OCIS_REPO)/ocis-pkg/config/defaults.BaseConfigPathValue=/etc/ocis" +# We can't link statically when vips is enabled but we still +# prefer static linking where possible +ifndef ENABLE_VIPS + DOCKER_LDFLAGS += -extldflags "-static" +endif + release-linux-docker-amd64: release-dirs GOOS=linux \ GOARCH=amd64 \ go build \ - -tags 'netgo $(TAGS)' \ + -tags 'netgo,$(TAGS)' \ -buildmode=pie \ -trimpath \ - -ldflags '-extldflags "-static" $(LDFLAGS) $(DOCKER_LDFLAGS)' \ + -ldflags '$(LDFLAGS) $(DOCKER_LDFLAGS)' \ -o '$(DIST)/binaries/$(EXECUTABLE)-linux-amd64' \ ./cmd/$(NAME) @@ -28,9 +34,9 @@ release-linux-docker-arm: release-dirs GOOS=linux \ GOARCH=arm \ go build \ - -tags 'netgo $(TAGS)' \ + -tags 'netgo,$(TAGS)' \ -trimpath \ - -ldflags '-extldflags "-static" $(LDFLAGS) $(DOCKER_LDFLAGS)' \ + -ldflags '$(LDFLAGS) $(DOCKER_LDFLAGS)' \ -o '$(DIST)/binaries/$(EXECUTABLE)-linux-arm' \ ./cmd/$(NAME) @@ -41,10 +47,10 @@ release-linux-docker-arm64: release-dirs GOOS=linux \ GOARCH=arm64 \ go build \ - -tags 'netgo $(TAGS)' \ + -tags 'netgo,$(TAGS)' \ -buildmode=pie \ -trimpath \ - -ldflags '-extldflags "-static" $(LDFLAGS) $(DOCKER_LDFLAGS)' \ + -ldflags '$(LDFLAGS) $(DOCKER_LDFLAGS)' \ -o '$(DIST)/binaries/$(EXECUTABLE)-linux-arm64' \ ./cmd/$(NAME) @@ -53,7 +59,7 @@ release-linux: release-dirs GOOS=linux \ GOARCH=amd64 \ go build \ - -tags 'netgo $(TAGS)' \ + -tags 'netgo,$(TAGS)' \ -buildmode=pie \ -trimpath \ -ldflags '-extldflags "-static" $(LDFLAGS)' \ @@ -63,7 +69,7 @@ release-linux: release-dirs GOOS=linux \ GOARCH=386 \ go build \ - -tags 'netgo $(TAGS)' \ + -tags 'netgo,$(TAGS)' \ -trimpath \ -ldflags '-extldflags "-static" $(LDFLAGS)' \ -o '$(DIST)/binaries/$(EXECUTABLE)-$(OUTPUT)-linux-386' \ @@ -72,7 +78,7 @@ release-linux: release-dirs GOOS=linux \ GOARCH=arm64 \ go build \ - -tags 'netgo $(TAGS)' \ + -tags 'netgo,$(TAGS)' \ -buildmode=pie \ -trimpath \ -ldflags '-extldflags "-static" $(LDFLAGS)' \ @@ -82,7 +88,7 @@ release-linux: release-dirs GOOS=linux \ GOARCH=arm \ go build \ - -tags 'netgo $(TAGS)' \ + -tags 'netgo,$(TAGS)' \ -trimpath \ -ldflags '-extldflags "-static" $(LDFLAGS)' \ -o '$(DIST)/binaries/$(EXECUTABLE)-$(OUTPUT)-linux-arm' \ @@ -96,7 +102,7 @@ release-darwin: release-dirs GOOS=darwin \ GOARCH=amd64 \ go build \ - -tags 'netgo $(TAGS)' \ + -tags 'netgo,$(TAGS)' \ -buildmode=pie \ -trimpath \ -ldflags '$(LDFLAGS)' \ @@ -106,7 +112,7 @@ release-darwin: release-dirs GOOS=darwin \ GOARCH=arm64 \ go build \ - -tags 'netgo $(TAGS)' \ + -tags 'netgo,$(TAGS)' \ -buildmode=pie \ -trimpath \ -ldflags '$(LDFLAGS)' \ diff --git a/Dockerfile b/Dockerfile index 5edcc0a5c94..ec23f79894a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,11 +26,11 @@ FROM owncloudci/golang:1.22 as build COPY --from=generate /ocis /ocis WORKDIR /ocis/ocis -RUN make ci-go-generate build +RUN make ci-go-generate build ENABLE_VIPS=true -FROM alpine:3.18 +FROM alpine:3.20 -RUN apk add --no-cache ca-certificates mailcap tree attr curl && \ +RUN apk add --no-cache ca-certificates mailcap tree attr curl vips && \ echo 'hosts: files dns' >| /etc/nsswitch.conf LABEL maintainer="ownCloud GmbH " \ diff --git a/ocis/docker/Dockerfile.linux.amd64 b/ocis/docker/Dockerfile.linux.amd64 index ff3cf2eae40..581de2d4713 100644 --- a/ocis/docker/Dockerfile.linux.amd64 +++ b/ocis/docker/Dockerfile.linux.amd64 @@ -1,9 +1,9 @@ -FROM amd64/alpine:3.18 +FROM amd64/alpine:3.20 ARG VERSION="" ARG REVISION="" -RUN apk add --no-cache ca-certificates mailcap tree attr curl inotify-tools bash libc6-compat && \ +RUN apk add --no-cache ca-certificates mailcap tree attr curl inotify-tools bash libc6-compat vips && \ echo 'hosts: files dns' >| /etc/nsswitch.conf LABEL maintainer="ownCloud GmbH " \ diff --git a/ocis/docker/Dockerfile.linux.arm64 b/ocis/docker/Dockerfile.linux.arm64 index 3ff8907f0bc..f154bda5155 100644 --- a/ocis/docker/Dockerfile.linux.arm64 +++ b/ocis/docker/Dockerfile.linux.arm64 @@ -1,9 +1,9 @@ -FROM arm64v8/alpine:3.18 +FROM arm64v8/alpine:3.20 ARG VERSION="" ARG REVISION="" -RUN apk add --no-cache ca-certificates mailcap tree attr curl inotify-tools bash libc6-compat && \ +RUN apk add --no-cache ca-certificates mailcap tree attr curl inotify-tools bash libc6-compat vips && \ echo 'hosts: files dns' >| /etc/nsswitch.conf LABEL maintainer="ownCloud GmbH " \ diff --git a/ocis/docker/Dockerfile.linux.debug.amd64 b/ocis/docker/Dockerfile.linux.debug.amd64 index cbf46d49bfb..8ee01193ea2 100644 --- a/ocis/docker/Dockerfile.linux.debug.amd64 +++ b/ocis/docker/Dockerfile.linux.debug.amd64 @@ -3,7 +3,7 @@ FROM amd64/alpine:latest ARG VERSION="" ARG REVISION="" -RUN apk add --no-cache ca-certificates mailcap tree attr curl libc6-compat delve && \ +RUN apk add --no-cache ca-certificates mailcap tree attr curl inotify-tools bash libc6-compat vips delve && \ echo 'hosts: files dns' >| /etc/nsswitch.conf LABEL maintainer="ownCloud GmbH " \ From 97f67d37574d9d65e0c6266d83825b5a6cd89f63 Mon Sep 17 00:00:00 2001 From: Ralf Haferkamp Date: Tue, 15 Oct 2024 18:27:40 +0200 Subject: [PATCH 06/11] Enable vips based thumbnailer for docker builds in the CI --- .drone.star | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.star b/.drone.star index df41d2300f4..102c53b7c57 100644 --- a/.drone.star +++ b/.drone.star @@ -1406,7 +1406,7 @@ def dockerRelease(ctx, arch, repo, build_type): "image": OC_CI_GOLANG, "environment": DRONE_HTTP_PROXY_ENV, "commands": [ - "make -C ocis release-linux-docker-%s" % (arch), + "make -C ocis release-linux-docker-%s ENABLE_VIPS=true" % (arch), ], }, { From b247cef715961c072cbed15ed07f91556a22f9be Mon Sep 17 00:00:00 2001 From: Ralf Haferkamp Date: Wed, 16 Oct 2024 12:05:40 +0200 Subject: [PATCH 07/11] Changelog entry and Readme updates related to libvips --- changelog/unreleased/thumbnails-libvips.md | 8 +++++++ services/thumbnails/README.md | 26 ++++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 changelog/unreleased/thumbnails-libvips.md diff --git a/changelog/unreleased/thumbnails-libvips.md b/changelog/unreleased/thumbnails-libvips.md new file mode 100644 index 00000000000..43cec8c8b65 --- /dev/null +++ b/changelog/unreleased/thumbnails-libvips.md @@ -0,0 +1,8 @@ +Enhancement: Allow to use libvips for generating thumbnails + +To improve performance (and to be able to support a wider range of images formats in the future) +the thumbnails service is now able to utilize libvips (https://www.libvips.org/) for generating thumbnails. +Enabling the use of libvips is implemented as a build-time option which is currently disabled for the +"bare-metal" build of the ocis binary and enabled for the docker image builds. + +https://github.com/owncloud/ocis/pull/10310 diff --git a/services/thumbnails/README.md b/services/thumbnails/README.md index d18177756b5..78cbea827ca 100644 --- a/services/thumbnails/README.md +++ b/services/thumbnails/README.md @@ -89,3 +89,29 @@ To have more control over memory (and CPU) consumption the maximum number of con ## Thumbnails and SecureView If a resource is shared using SecureView, the share reciever will get a 403 (forbidden) response when requesting a thumbnail. The requesting client needs to decide what to show and usually a placeholder thumbnail is used. + +## Using libvips for thumbnail generation + +To improve performance (and to support a wider range of images formats) the thumbnails service is able to utilize libvips (https://www.libvips.org/) for thumbnail generation. Support for libvips needs to be +enabled at buildtime and has a couple of implications: + +* With libvips support enabled it is not possible to create a statically linked ocis binary +* So the libvips shared libraries need to be available at runtime in the same release that was used to build the ocis binary +* When using the ocis docker images the libvips shared libraries are included in the image + +Support of libvips is disabled by default. To enable it make sure libvips and its buildtime dependencies are install in your build environment. Then just set +the `ENABLE_VIPS` variable on the `make` command: + +```shell +make -C ocis build ENABLE_VIPS=1 +``` + +Or include the `enable_vips` build tag in the `go build` command: + +```shell +go build -tags enable_vips -o ocis -o bin/ocis ./cmd/ocis +``` + +When building a docker image using the Dockerfile in the top-level directory of ocis, libvips support is enabled and the libvips shared libraries are included +in the resulting docker image. + From 9cdc9788063a070a765a3970dea7c0f5cc0069c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Wed, 16 Oct 2024 14:19:24 +0200 Subject: [PATCH 08/11] add buildFlags comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- .vscode/launch.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.vscode/launch.json b/.vscode/launch.json index cbe767ab9e0..3cd0bdb5dc3 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -18,6 +18,9 @@ "type": "go", "request": "launch", "mode": "debug", + "buildFlags": [ + // "-tags", "enable_vips" + ], "program": "${workspaceFolder}/ocis/cmd/ocis", "args": [ "server" From d12a065bfc432990511b953fbd447d9bbf49df7f Mon Sep 17 00:00:00 2001 From: Ralf Haferkamp Date: Wed, 16 Oct 2024 16:03:19 +0200 Subject: [PATCH 09/11] Apply suggestions from proof reading Co-authored-by: Martin --- services/thumbnails/README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/services/thumbnails/README.md b/services/thumbnails/README.md index 78cbea827ca..1897878d0df 100644 --- a/services/thumbnails/README.md +++ b/services/thumbnails/README.md @@ -90,17 +90,17 @@ To have more control over memory (and CPU) consumption the maximum number of con If a resource is shared using SecureView, the share reciever will get a 403 (forbidden) response when requesting a thumbnail. The requesting client needs to decide what to show and usually a placeholder thumbnail is used. -## Using libvips for thumbnail generation +## Using libvips for Thumbnail Generation -To improve performance (and to support a wider range of images formats) the thumbnails service is able to utilize libvips (https://www.libvips.org/) for thumbnail generation. Support for libvips needs to be +To improve performance and to support a wider range of images formats, the thumbnails service is able to utilize the [libvips library](https://www.libvips.org/) for thumbnail generation. Support for libvips needs to be enabled at buildtime and has a couple of implications: -* With libvips support enabled it is not possible to create a statically linked ocis binary -* So the libvips shared libraries need to be available at runtime in the same release that was used to build the ocis binary -* When using the ocis docker images the libvips shared libraries are included in the image +* With libvips support enabled, it is not possible to create a statically linked ocis binary. +* Therefore, the libvips shared libraries need to be available at runtime in the same release that was used to build the ocis binary. +* When using the ocis docker images, the libvips shared libraries are included in the image and are correctly embedded. -Support of libvips is disabled by default. To enable it make sure libvips and its buildtime dependencies are install in your build environment. Then just set -the `ENABLE_VIPS` variable on the `make` command: +Support of libvips is disabled by default. To enable it, make sure libvips and its buildtime dependencies are installed in your build environment. +Then you just need to set the `ENABLE_VIPS` variable on the `make` command: ```shell make -C ocis build ENABLE_VIPS=1 From 04f6c17bb8fa90ccd3b2b0666c4f363fe58f939a Mon Sep 17 00:00:00 2001 From: Ralf Haferkamp Date: Thu, 17 Oct 2024 10:31:21 +0200 Subject: [PATCH 10/11] Fix GeoGebra thumbnails when libvips support is enabled For GeoGebra files we where still using the imaging package to decode the embedded png, but handed it of to vips for scaling, which can't work. Now we use vips for decoding the image as well. --- services/thumbnails/pkg/errors/error.go | 2 ++ services/thumbnails/pkg/preprocessor/preprocessor.go | 8 +++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/services/thumbnails/pkg/errors/error.go b/services/thumbnails/pkg/errors/error.go index e8bbe308896..c411130ce5c 100644 --- a/services/thumbnails/pkg/errors/error.go +++ b/services/thumbnails/pkg/errors/error.go @@ -11,6 +11,8 @@ var ( ErrNoEncoderForType = errors.New("thumbnails: no encoder for this type found") // ErrNoImageFromAudioFile defines an error when an image cannot be extracted from an audio file ErrNoImageFromAudioFile = errors.New("thumbnails: could not extract image from audio file") + // ErrNoConverterForExtractedImageFromGgsFile defines an error when the extracted image from an ggs file could not be converted + ErrNoConverterForExtractedImageFromGgsFile = errors.New("thumbnails: could not find converter for image extracted from ggs file") // ErrNoConverterForExtractedImageFromAudioFile defines an error when the extracted image from an audio file could not be converted ErrNoConverterForExtractedImageFromAudioFile = errors.New("thumbnails: could not find converter for image extracted from audio file") // ErrCS3AuthorizationMissing defines an error when the CS3 authorization is missing diff --git a/services/thumbnails/pkg/preprocessor/preprocessor.go b/services/thumbnails/pkg/preprocessor/preprocessor.go index d4626b11e24..1733dcf8353 100644 --- a/services/thumbnails/pkg/preprocessor/preprocessor.go +++ b/services/thumbnails/pkg/preprocessor/preprocessor.go @@ -12,7 +12,6 @@ import ( "mime" "strings" - "github.com/kovidgoyal/imaging" "github.com/pkg/errors" "golang.org/x/image/font" "golang.org/x/image/font/opentype" @@ -60,8 +59,11 @@ func (g GgsDecoder) Convert(r io.Reader) (interface{}, error) { if err != nil { return nil, err } - - img, err := imaging.Decode(thumbnail, imaging.AutoOrientation(true)) + converter := ForType("image/png", nil) + if converter == nil { + return nil, thumbnailerErrors.ErrNoConverterForExtractedImageFromGgsFile + } + img, err := converter.Convert(thumbnail) if err != nil { return nil, errors.Wrap(err, `could not decode the image`) } From ced66358edf3eb103cc1720328f552d74c11cec1 Mon Sep 17 00:00:00 2001 From: Ralf Haferkamp Date: Thu, 17 Oct 2024 12:22:39 +0200 Subject: [PATCH 11/11] Fix libvips thumbnail generation for text files --- .../pkg/thumbnail/generator_vips.go | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/services/thumbnails/pkg/thumbnail/generator_vips.go b/services/thumbnails/pkg/thumbnail/generator_vips.go index c2c1bd37154..87e6ec2578b 100644 --- a/services/thumbnails/pkg/thumbnail/generator_vips.go +++ b/services/thumbnails/pkg/thumbnail/generator_vips.go @@ -3,11 +3,13 @@ package thumbnail import ( + "bytes" "image" "strings" "github.com/davidbyttow/govips/v2/vips" "github.com/owncloud/ocis/v2/services/thumbnails/pkg/errors" + "golang.org/x/image/bmp" ) // SimpleGenerator is the default image generator and is used for all image types expect gif. @@ -26,7 +28,7 @@ func NewSimpleGenerator(filetype, process string) (SimpleGenerator, error) { case "resize": return SimpleGenerator{crop: vips.InterestingNone, process: process, size: vips.SizeForce}, nil default: - return SimpleGenerator{crop: vips.InterestingNone, process: process}, nil + return SimpleGenerator{crop: vips.InterestingAttention, process: process, size: vips.SizeBoth}, nil } } @@ -37,8 +39,23 @@ func (g SimpleGenerator) ProcessorID() string { // Generate generates a alternative image version. func (g SimpleGenerator) Generate(size image.Rectangle, img interface{}) (interface{}, error) { - m, ok := img.(*vips.ImageRef) - if !ok { + var m *vips.ImageRef + var err error + switch img.(type) { + case *image.RGBA: + // This comes from the txt preprocessor + var buf bytes.Buffer + if err = bmp.Encode(&buf, img.(*image.RGBA)); err != nil { + return nil, err + } + m, err = vips.NewImageFromReader(&buf) + if err != nil { + return nil, err + } + + case *vips.ImageRef: + m = img.(*vips.ImageRef) + default: return nil, errors.ErrInvalidType } @@ -54,9 +71,14 @@ func (g SimpleGenerator) Generate(size image.Rectangle, img interface{}) (interf } func (g SimpleGenerator) Dimensions(img interface{}) (image.Rectangle, error) { - m, ok := img.(*vips.ImageRef) - if !ok { + switch img.(type) { + case *image.RGBA: + m := img.(*image.RGBA) + return m.Bounds(), nil + case *vips.ImageRef: + m := img.(*vips.ImageRef) + return image.Rect(0, 0, m.Width(), m.Height()), nil + default: return image.Rectangle{}, errors.ErrInvalidType } - return image.Rect(0, 0, m.Width(), m.Height()), nil }