mirror of
https://github.com/talgo-cloud/bimg.git
synced 2026-03-16 02:45:54 -07:00
Merge pull request #67 from h2non/master
merge(master): update develop branch
This commit is contained in:
commit
099bc57753
17 changed files with 1560 additions and 1008 deletions
|
|
@ -5,9 +5,4 @@ go:
|
||||||
- release
|
- release
|
||||||
- tip
|
- tip
|
||||||
before_install:
|
before_install:
|
||||||
- go get github.com/axw/gocov/gocov
|
|
||||||
- go get github.com/mattn/goveralls
|
|
||||||
- if ! go get code.google.com/p/go.tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi
|
|
||||||
- curl -s https://raw.githubusercontent.com/lovell/sharp/master/preinstall.sh | sudo bash -
|
- curl -s https://raw.githubusercontent.com/lovell/sharp/master/preinstall.sh | sudo bash -
|
||||||
script:
|
|
||||||
- $HOME/gopath/bin/goveralls -service=travis-ci
|
|
||||||
|
|
|
||||||
510
README.md
510
README.md
|
|
@ -1,19 +1,44 @@
|
||||||
# bimg [](https://travis-ci.org/h2non/bimg) [](https://github.com/h2non/bimg/releases) [](https://godoc.org/github.com/h2non/bimg) [](https://coveralls.io/r/h2non/bimg?branch=master)
|
# bimg [](https://travis-ci.org/h2non/bimg) [](https://github.com/h2non/bimg/releases) [](https://godoc.org/github.com/h2non/bimg)
|
||||||
|
|
||||||
Small [Go](http://golang.org) library for blazing fast and efficient image processing based on [libvips](https://github.com/jcupitt/libvips) using C bindings. It provides a clean, simple and fluent [API](https://godoc.org/github.com/h2non/bimg) in pure Go.
|
Small [Go](http://golang.org) package for fast high-level image processing using [libvips](https://github.com/jcupitt/libvips) via C bindings, providing a simple, elegant and fluent [programmatic API](#examples).
|
||||||
|
|
||||||
bimg is designed to be a small and efficient library with a generic and useful set of features.
|
bimg was designed to be a small and efficient library supporting a common set of [image operations](#supported-image-operations) such as crop, resize, rotate, zoom or watermark. It can read JPEG, PNG, WEBP and TIFF formats and output to JPEG, PNG and WEBP, including conversion between them.
|
||||||
It uses internally libvips, which requires a [low memory footprint](http://www.vips.ecs.soton.ac.uk/index.php?title=Speed_and_Memory_Use)
|
|
||||||
and it's typically 4x faster than using the quickest ImageMagick and GraphicsMagick settings or Go native `image` package, and in some cases it's even 8x faster processing JPEG images.
|
|
||||||
|
|
||||||
It can read JPEG, PNG, WEBP and TIFF formats and output to JPEG, PNG and WEBP. It supports common [image transformation](#supported-image-operations) operations such as crop, resize, rotate, zoom, watermark... and conversion between multiple formats.
|
bimg uses internally libvips, a powerful library written in C for image processing which requires a [low memory footprint](http://www.vips.ecs.soton.ac.uk/index.php?title=Speed_and_Memory_Use)
|
||||||
|
and it's typically 4x faster than using the quickest ImageMagick and GraphicsMagick settings or Go native `image` package, and in some cases it's even 8x faster processing JPEG images.
|
||||||
|
|
||||||
For getting started, take a look to the [examples](#examples) and [programmatic API](https://godoc.org/github.com/h2non/bimg) documentation.
|
If you're looking for an HTTP based image processing solution, see [imaginary](https://github.com/h2non/imaginary).
|
||||||
|
|
||||||
bimg was heavily inspired in [sharp](https://github.com/lovell/sharp),
|
bimg was heavily inspired in [sharp](https://github.com/lovell/sharp), its homologous package built for [node.js](http://nodejs.org).
|
||||||
its homologous package built for node.js by [Lovell Fuller](https://github.com/lovell).
|
|
||||||
|
|
||||||
**Note**: bimg is still beta. Pull request and issues are highly appreciated
|
## Contents
|
||||||
|
|
||||||
|
- [Supported image operations](#supported-image-operations)
|
||||||
|
- [Prerequisites](#prerequisites)
|
||||||
|
- [Installation](#installation)
|
||||||
|
- [Performance](#performance)
|
||||||
|
- [Benchmark](#benchmark)
|
||||||
|
- [Examples](#examples)
|
||||||
|
- [Debugging](#debugging)
|
||||||
|
- [API](#api)
|
||||||
|
- [Credits](#credits)
|
||||||
|
|
||||||
|
## Supported image operations
|
||||||
|
|
||||||
|
- Resize
|
||||||
|
- Enlarge
|
||||||
|
- Crop
|
||||||
|
- Rotate (with auto-rotate based on EXIF orientation)
|
||||||
|
- Flip (with auto-flip based on EXIF metadata)
|
||||||
|
- Flop
|
||||||
|
- Zoom
|
||||||
|
- Thumbnail
|
||||||
|
- Extract area
|
||||||
|
- Watermark (text only)
|
||||||
|
- Gaussian blur effect
|
||||||
|
- Custom output color space (RGB, grayscale...)
|
||||||
|
- Format conversion (with additional quality/compression settings)
|
||||||
|
- EXIF metadata (size, alpha channel, profile, orientation...)
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
|
|
@ -34,22 +59,14 @@ Run the following script as `sudo` (supports OSX, Debian/Ubuntu, Redhat, Fedora,
|
||||||
curl -s https://raw.githubusercontent.com/lovell/sharp/master/preinstall.sh | sudo bash -
|
curl -s https://raw.githubusercontent.com/lovell/sharp/master/preinstall.sh | sudo bash -
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If you wanna take the advantage of [OpenSlide](http://openslide.org/), simply add `--with-openslide` to enable it:
|
||||||
|
```bash
|
||||||
|
curl -s https://raw.githubusercontent.com/lovell/sharp/master/preinstall.sh | sudo bash -s --with-openslide
|
||||||
|
```
|
||||||
|
|
||||||
The [install script](https://github.com/lovell/sharp/blob/master/preinstall.sh) requires `curl` and `pkg-config`
|
The [install script](https://github.com/lovell/sharp/blob/master/preinstall.sh) requires `curl` and `pkg-config`
|
||||||
|
|
||||||
## Supported image operations
|
For platform specific installations, see [Mac OS](https://github.com/lovell/sharp/blob/master/README.md#mac-os-tips) tips or [Windows](https://github.com/lovell/sharp/blob/master/README.md#windows) tips
|
||||||
|
|
||||||
- Resize
|
|
||||||
- Enlarge
|
|
||||||
- Crop
|
|
||||||
- Rotate (with auto-rotate based on EXIF orientation)
|
|
||||||
- Flip (with auto-flip based on EXIF metadata)
|
|
||||||
- Flop
|
|
||||||
- Zoom
|
|
||||||
- Thumbnail
|
|
||||||
- Extract area
|
|
||||||
- Watermark (fully customizable text-based)
|
|
||||||
- Format conversion (with additional quality/compression settings)
|
|
||||||
- EXIF metadata (size, alpha channel, profile, orientation...)
|
|
||||||
|
|
||||||
## Performance
|
## Performance
|
||||||
|
|
||||||
|
|
@ -59,23 +76,32 @@ Here you can see some performance test comparisons for multiple scenarios:
|
||||||
- [libvips speed and memory usage](http://www.vips.ecs.soton.ac.uk/index.php?title=Speed_and_Memory_Use)
|
- [libvips speed and memory usage](http://www.vips.ecs.soton.ac.uk/index.php?title=Speed_and_Memory_Use)
|
||||||
- [sharp performance tests](https://github.com/lovell/sharp#the-task)
|
- [sharp performance tests](https://github.com/lovell/sharp#the-task)
|
||||||
|
|
||||||
#### Benchmarks
|
## Benchmark
|
||||||
|
|
||||||
Tested using Go 1.4 and libvips-7.42.3 in OSX i7 2.7Ghz
|
Tested using Go 1.5.1 and libvips-7.42.3 in OSX i7 2.7Ghz
|
||||||
```
|
```
|
||||||
PASS
|
BenchmarkRotateJpeg-8 20 64686945 ns/op
|
||||||
BenchmarkResizeLargeJpeg 30 46652408 ns/op
|
BenchmarkResizeLargeJpeg-8 20 63390416 ns/op
|
||||||
BenchmarkResizePng 20 57387902 ns/op
|
BenchmarkResizePng-8 100 18147294 ns/op
|
||||||
BenchmarkResizeWebP 500 2453220 ns/op
|
BenchmarkResizeWebP-8 100 20836741 ns/op
|
||||||
BenchmarkConvertToJpeg 30 35556414 ns/op
|
BenchmarkConvertToJpeg-8 100 12831812 ns/op
|
||||||
BenchmarkCrop 30 51768475 ns/op
|
BenchmarkConvertToPng-8 10 128901422 ns/op
|
||||||
BenchmarkExtract 30 50866406 ns/op
|
BenchmarkConvertToWebp-8 10 204027990 ns/op
|
||||||
ok 9.424s
|
BenchmarkCropJpeg-8 30 59068572 ns/op
|
||||||
|
BenchmarkCropPng-8 10 117303259 ns/op
|
||||||
|
BenchmarkCropWebP-8 10 107060659 ns/op
|
||||||
|
BenchmarkExtractJpeg-8 50 30708919 ns/op
|
||||||
|
BenchmarkExtractPng-8 3000 595546 ns/op
|
||||||
|
BenchmarkExtractWebp-8 3000 386379 ns/op
|
||||||
|
BenchmarkZoomJpeg-8 10 160005424 ns/op
|
||||||
|
BenchmarkZoomPng-8 30 44561047 ns/op
|
||||||
|
BenchmarkZoomWebp-8 10 126732678 ns/op
|
||||||
|
BenchmarkWatermarkJpeg-8 20 79006133 ns/op
|
||||||
|
BenchmarkWatermarPng-8 200 8197291 ns/op
|
||||||
|
BenchmarkWatermarWebp-8 30 49360369 ns/op
|
||||||
```
|
```
|
||||||
|
|
||||||
## API
|
## Examples
|
||||||
|
|
||||||
### Examples
|
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import (
|
import (
|
||||||
|
|
@ -140,6 +166,46 @@ if bimg.NewImage(newImage).Type() == "png" {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Force resize
|
||||||
|
|
||||||
|
Force resize operation without perserving the aspect ratio:
|
||||||
|
|
||||||
|
```go
|
||||||
|
buffer, err := bimg.Read("image.jpg")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
newImage, err := bimg.NewImage(buffer).ForceResize(1000, 500)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
size := bimg.Size(newImage)
|
||||||
|
if size.Width != 1000 || size.Height != 500 {
|
||||||
|
fmt.Fprintln(os.Stderr, "Incorrect image size")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Custom colour space (black & white)
|
||||||
|
|
||||||
|
```go
|
||||||
|
buffer, err := bimg.Read("image.jpg")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
newImage, err := bimg.NewImage(buffer).Colourspace(bimg.INTERPRETATION_B_W)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
colourSpace, _ := bimg.ImageInterpretation(newImage)
|
||||||
|
if colourSpace != bimg.INTERPRETATION_B_W {
|
||||||
|
fmt.Fprintln(os.Stderr, "Invalid colour space")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
#### Custom options
|
#### Custom options
|
||||||
|
|
||||||
See [Options](https://godoc.org/github.com/h2non/bimg#Options) struct to discover all the available fields
|
See [Options](https://godoc.org/github.com/h2non/bimg#Options) struct to discover all the available fields
|
||||||
|
|
@ -151,6 +217,7 @@ options := bimg.Options{
|
||||||
Crop: true,
|
Crop: true,
|
||||||
Quality: 95,
|
Quality: 95,
|
||||||
Rotate: 180,
|
Rotate: 180,
|
||||||
|
Interlace: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer, err := bimg.Read("image.jpg")
|
buffer, err := bimg.Read("image.jpg")
|
||||||
|
|
@ -174,19 +241,17 @@ if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
options := bimg.Watermark{
|
watermark := bimg.Watermark{
|
||||||
Watermark{
|
Text: "Chuck Norris (c) 2315",
|
||||||
Text: "Chuck Norris - Copyright (c) 2315",
|
Opacity: 0.25,
|
||||||
Opacity: 0.25,
|
Width: 200,
|
||||||
Width: 200,
|
DPI: 100,
|
||||||
DPI: 100,
|
Margin: 150,
|
||||||
Margin: 150,
|
Font: "sans bold 12",
|
||||||
Font: "sans bold 12",
|
Background: bimg.Color{255, 255, 255},
|
||||||
Background: bimg.Color{255, 255, 255},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
newImage, err := bimg.NewImage(buffer).Watermark()
|
newImage, err := bimg.NewImage(buffer).Watermark(watermark)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
}
|
}
|
||||||
|
|
@ -220,349 +285,36 @@ if err != nil {
|
||||||
bimg.Write("new.jpg", newImage)
|
bimg.Write("new.jpg", newImage)
|
||||||
```
|
```
|
||||||
|
|
||||||
#### func DetermineImageTypeName
|
## Debugging
|
||||||
|
|
||||||
```go
|
Run the process passing the `DEBUG` environment variable
|
||||||
func DetermineImageTypeName(buf []byte) string
|
|
||||||
```
|
```
|
||||||
Determines the image type format by name (jpeg, png, webp or tiff)
|
DEBUG=bimg ./app
|
||||||
|
|
||||||
#### func Initialize
|
|
||||||
|
|
||||||
```go
|
|
||||||
func Initialize()
|
|
||||||
```
|
|
||||||
Explicit thread-safe start of libvips. You should only call this function if you
|
|
||||||
previously shutdown libvips
|
|
||||||
|
|
||||||
#### func IsTypeNameSupported
|
|
||||||
|
|
||||||
```go
|
|
||||||
func IsTypeNameSupported(t string) bool
|
|
||||||
```
|
|
||||||
Check if a given image type name is supported
|
|
||||||
|
|
||||||
#### func IsTypeSupported
|
|
||||||
|
|
||||||
```go
|
|
||||||
func IsTypeSupported(t ImageType) bool
|
|
||||||
```
|
|
||||||
Check if a given image type is supported
|
|
||||||
|
|
||||||
#### func Read
|
|
||||||
|
|
||||||
```go
|
|
||||||
func Read(path string) ([]byte, error)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### func Resize
|
Enable libvips traces (note that a lot of data will be written in stdout):
|
||||||
|
```
|
||||||
```go
|
VIPS_TRACE=1 ./app
|
||||||
func Resize(buf []byte, o Options) ([]byte, error)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### func Shutdown
|
## API
|
||||||
|
|
||||||
```go
|
See [godoc reference](https://godoc.org/github.com/h2non/bimg) for detailed API documentation.
|
||||||
func Shutdown()
|
|
||||||
```
|
|
||||||
Explicit thread-safe libvips shutdown. Call this to drop caches. If libvips was
|
|
||||||
already initialized, the function is no-op
|
|
||||||
|
|
||||||
#### func VipsDebug
|
## Credits
|
||||||
|
|
||||||
```go
|
People who recurrently contributed to improve `bimg` in some way.
|
||||||
func VipsDebug()
|
|
||||||
```
|
|
||||||
Output to stdout collected data for debugging purposes
|
|
||||||
|
|
||||||
#### func Write
|
|
||||||
|
|
||||||
```go
|
|
||||||
func Write(path string, buf []byte) error
|
|
||||||
```
|
|
||||||
|
|
||||||
#### type Angle
|
|
||||||
|
|
||||||
```go
|
|
||||||
type Angle int
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```go
|
|
||||||
const (
|
|
||||||
D0 Angle = C.VIPS_ANGLE_D0
|
|
||||||
D90 Angle = C.VIPS_ANGLE_D90
|
|
||||||
D180 Angle = C.VIPS_ANGLE_D180
|
|
||||||
D270 Angle = C.VIPS_ANGLE_D270
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### type Direction
|
|
||||||
|
|
||||||
```go
|
|
||||||
type Direction int
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```go
|
|
||||||
const (
|
|
||||||
HORIZONTAL Direction = C.VIPS_DIRECTION_HORIZONTAL
|
|
||||||
VERTICAL Direction = C.VIPS_DIRECTION_VERTICAL
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### type Gravity
|
|
||||||
|
|
||||||
```go
|
|
||||||
type Gravity int
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```go
|
|
||||||
const (
|
|
||||||
CENTRE Gravity = iota
|
|
||||||
NORTH
|
|
||||||
EAST
|
|
||||||
SOUTH
|
|
||||||
WEST
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### type Image
|
|
||||||
|
|
||||||
```go
|
|
||||||
type Image struct {
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
#### func NewImage
|
|
||||||
|
|
||||||
```go
|
|
||||||
func NewImage(buf []byte) *Image
|
|
||||||
```
|
|
||||||
Creates a new image
|
|
||||||
|
|
||||||
#### func (*Image) Convert
|
|
||||||
|
|
||||||
```go
|
|
||||||
func (i *Image) Convert(t ImageType) ([]byte, error)
|
|
||||||
```
|
|
||||||
Convert image to another format
|
|
||||||
|
|
||||||
#### func (*Image) Crop
|
|
||||||
|
|
||||||
```go
|
|
||||||
func (i *Image) Crop(width, height int, gravity Gravity) ([]byte, error)
|
|
||||||
```
|
|
||||||
Crop the image to the exact size specified
|
|
||||||
|
|
||||||
#### func (*Image) CropByHeight
|
|
||||||
|
|
||||||
```go
|
|
||||||
func (i *Image) CropByHeight(height int) ([]byte, error)
|
|
||||||
```
|
|
||||||
Crop an image by height (auto width)
|
|
||||||
|
|
||||||
#### func (*Image) CropByWidth
|
|
||||||
|
|
||||||
```go
|
|
||||||
func (i *Image) CropByWidth(width int) ([]byte, error)
|
|
||||||
```
|
|
||||||
Crop an image by width (auto height)
|
|
||||||
|
|
||||||
#### func (*Image) Enlarge
|
|
||||||
|
|
||||||
```go
|
|
||||||
func (i *Image) Enlarge(width, height int) ([]byte, error)
|
|
||||||
```
|
|
||||||
Enlarge the image from the by X/Y axis
|
|
||||||
|
|
||||||
#### func (*Image) Extract
|
|
||||||
|
|
||||||
```go
|
|
||||||
func (i *Image) Extract(top, left, width, height int) ([]byte, error)
|
|
||||||
```
|
|
||||||
Extract area from the by X/Y axis
|
|
||||||
|
|
||||||
#### func (*Image) Flip
|
|
||||||
|
|
||||||
```go
|
|
||||||
func (i *Image) Flip() ([]byte, error)
|
|
||||||
```
|
|
||||||
Flip the image about the vertical Y axis
|
|
||||||
|
|
||||||
#### func (*Image) Flop
|
|
||||||
|
|
||||||
```go
|
|
||||||
func (i *Image) Flop() ([]byte, error)
|
|
||||||
```
|
|
||||||
Flop the image about the horizontal X axis
|
|
||||||
|
|
||||||
#### func (*Image) Metadata
|
|
||||||
|
|
||||||
```go
|
|
||||||
func (i *Image) Metadata() (ImageMetadata, error)
|
|
||||||
```
|
|
||||||
Get image metadata (size, alpha channel, profile, EXIF rotation)
|
|
||||||
|
|
||||||
#### func (*Image) Process
|
|
||||||
|
|
||||||
```go
|
|
||||||
func (i *Image) Process(o Options) ([]byte, error)
|
|
||||||
```
|
|
||||||
Transform the image by custom options
|
|
||||||
|
|
||||||
#### func (*Image) Resize
|
|
||||||
|
|
||||||
```go
|
|
||||||
func (i *Image) Resize(width, height int) ([]byte, error)
|
|
||||||
```
|
|
||||||
Resize the image to fixed width and height
|
|
||||||
|
|
||||||
#### func (*Image) Rotate
|
|
||||||
|
|
||||||
```go
|
|
||||||
func (i *Image) Rotate(a Angle) ([]byte, error)
|
|
||||||
```
|
|
||||||
Rotate the image by given angle degrees (0, 90, 180 or 270)
|
|
||||||
|
|
||||||
#### func (*Image) Size
|
|
||||||
|
|
||||||
```go
|
|
||||||
func (i *Image) Size() (ImageSize, error)
|
|
||||||
```
|
|
||||||
Get image size
|
|
||||||
|
|
||||||
#### func (*Image) Thumbnail
|
|
||||||
|
|
||||||
```go
|
|
||||||
func (i *Image) Thumbnail(pixels int) ([]byte, error)
|
|
||||||
```
|
|
||||||
Thumbnail the image by the a given width by aspect ratio 4:4
|
|
||||||
|
|
||||||
#### func (*Image) Type
|
|
||||||
|
|
||||||
```go
|
|
||||||
func (i *Image) Type() string
|
|
||||||
```
|
|
||||||
Get image type format (jpeg, png, webp, tiff)
|
|
||||||
|
|
||||||
#### type ImageMetadata
|
|
||||||
|
|
||||||
```go
|
|
||||||
type ImageMetadata struct {
|
|
||||||
Orientation int
|
|
||||||
Channels int
|
|
||||||
Alpha bool
|
|
||||||
Profile bool
|
|
||||||
Type string
|
|
||||||
Space string
|
|
||||||
Size ImageSize
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
#### func Metadata
|
|
||||||
|
|
||||||
```go
|
|
||||||
func Metadata(buf []byte) (ImageMetadata, error)
|
|
||||||
```
|
|
||||||
Extract the image metadata (size, type, alpha channel, profile, EXIF
|
|
||||||
orientation...)
|
|
||||||
|
|
||||||
#### type ImageSize
|
|
||||||
|
|
||||||
```go
|
|
||||||
type ImageSize struct {
|
|
||||||
Width int
|
|
||||||
Height int
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
#### func Size
|
|
||||||
|
|
||||||
```go
|
|
||||||
func Size(buf []byte) (ImageSize, error)
|
|
||||||
```
|
|
||||||
Get the image size by width and height pixels
|
|
||||||
|
|
||||||
#### type ImageType
|
|
||||||
|
|
||||||
```go
|
|
||||||
type ImageType int
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```go
|
|
||||||
const (
|
|
||||||
UNKNOWN ImageType = iota
|
|
||||||
JPEG
|
|
||||||
WEBP
|
|
||||||
PNG
|
|
||||||
TIFF
|
|
||||||
MAGICK
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### func DetermineImageType
|
|
||||||
|
|
||||||
```go
|
|
||||||
func DetermineImageType(buf []byte) ImageType
|
|
||||||
```
|
|
||||||
Determines the image type format (jpeg, png, webp or tiff)
|
|
||||||
|
|
||||||
#### type Interpolator
|
|
||||||
|
|
||||||
```go
|
|
||||||
type Interpolator int
|
|
||||||
```
|
|
||||||
|
|
||||||
```go
|
|
||||||
const (
|
|
||||||
BICUBIC Interpolator = iota
|
|
||||||
BILINEAR
|
|
||||||
NOHALO
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### func (Interpolator) String
|
|
||||||
|
|
||||||
```go
|
|
||||||
func (i Interpolator) String() string
|
|
||||||
```
|
|
||||||
|
|
||||||
#### type Options
|
|
||||||
|
|
||||||
```go
|
|
||||||
type Options struct {
|
|
||||||
Height int
|
|
||||||
Width int
|
|
||||||
AreaHeight int
|
|
||||||
AreaWidth int
|
|
||||||
Top int
|
|
||||||
Left int
|
|
||||||
Extend int
|
|
||||||
Quality int
|
|
||||||
Compression int
|
|
||||||
Crop bool
|
|
||||||
Enlarge bool
|
|
||||||
Embed bool
|
|
||||||
Flip bool
|
|
||||||
Flop bool
|
|
||||||
Rotate Angle
|
|
||||||
Gravity Gravity
|
|
||||||
Type ImageType
|
|
||||||
Interpolator Interpolator
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Special Thanks
|
|
||||||
|
|
||||||
- [John Cupitt](https://github.com/jcupitt)
|
- [John Cupitt](https://github.com/jcupitt)
|
||||||
|
- [Yoan Blanc](https://github.com/greut)
|
||||||
|
- [Christophe Eblé](https://github.com/chreble)
|
||||||
|
- [Brant Fitzsimmons](https://github.com/bfitzsimmons)
|
||||||
|
- [Thomas Meson](https://github.com/zllak)
|
||||||
|
|
||||||
|
Thank you!
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
MIT - Tomas Aparicio
|
MIT - Tomas Aparicio
|
||||||
|
|
||||||
|
[](https://sourcegraph.com/github.com/h2non/bimg)
|
||||||
|
|
|
||||||
59
debug.go
59
debug.go
|
|
@ -1,62 +1,5 @@
|
||||||
package bimg
|
package bimg
|
||||||
|
|
||||||
import (
|
import . "github.com/tj/go-debug"
|
||||||
"github.com/dustin/go-humanize"
|
|
||||||
. "github.com/tj/go-debug"
|
|
||||||
"runtime"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var debug = Debug("bimg")
|
var debug = Debug("bimg")
|
||||||
|
|
||||||
// Print Go memory and garbage collector stats. Useful for debugging
|
|
||||||
func PrintMemoryStats() {
|
|
||||||
log := Debug("memory")
|
|
||||||
mem := memoryStats()
|
|
||||||
|
|
||||||
log("\u001b[33m---- Memory Dump Stats ----\u001b[39m")
|
|
||||||
log("Allocated: %s", humanize.Bytes(mem.Alloc))
|
|
||||||
log("Total Allocated: %s", humanize.Bytes(mem.TotalAlloc))
|
|
||||||
log("Memory Allocations: %d", mem.Mallocs)
|
|
||||||
log("Memory Frees: %d", mem.Frees)
|
|
||||||
log("Heap Allocated: %s", humanize.Bytes(mem.HeapAlloc))
|
|
||||||
log("Heap System: %s", humanize.Bytes(mem.HeapSys))
|
|
||||||
log("Heap In Use: %s", humanize.Bytes(mem.HeapInuse))
|
|
||||||
log("Heap Idle: %s", humanize.Bytes(mem.HeapIdle))
|
|
||||||
log("Heap OS Related: %s", humanize.Bytes(mem.HeapReleased))
|
|
||||||
log("Heap Objects: %s", humanize.Bytes(mem.HeapObjects))
|
|
||||||
log("Stack In Use: %s", humanize.Bytes(mem.StackInuse))
|
|
||||||
log("Stack System: %s", humanize.Bytes(mem.StackSys))
|
|
||||||
log("Stack Span In Use: %s", humanize.Bytes(mem.MSpanInuse))
|
|
||||||
log("Stack Cache In Use: %s", humanize.Bytes(mem.MCacheInuse))
|
|
||||||
log("Next GC cycle: %s", humanizeNano(mem.NextGC))
|
|
||||||
log("Last GC cycle: %s", humanize.Time(time.Unix(0, int64(mem.LastGC))))
|
|
||||||
log("\u001b[33m---- End Memory Dump ----\u001b[39m")
|
|
||||||
}
|
|
||||||
|
|
||||||
func memoryStats() runtime.MemStats {
|
|
||||||
var mem runtime.MemStats
|
|
||||||
runtime.ReadMemStats(&mem)
|
|
||||||
return mem
|
|
||||||
}
|
|
||||||
|
|
||||||
func humanizeNano(n uint64) string {
|
|
||||||
var suffix string
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case n > 1e9:
|
|
||||||
n /= 1e9
|
|
||||||
suffix = "s"
|
|
||||||
case n > 1e6:
|
|
||||||
n /= 1e6
|
|
||||||
suffix = "ms"
|
|
||||||
case n > 1e3:
|
|
||||||
n /= 1e3
|
|
||||||
suffix = "us"
|
|
||||||
default:
|
|
||||||
suffix = "ns"
|
|
||||||
}
|
|
||||||
|
|
||||||
return strconv.Itoa(int(n)) + suffix
|
|
||||||
}
|
|
||||||
|
|
|
||||||
18
file.go
18
file.go
|
|
@ -1,23 +1,9 @@
|
||||||
package bimg
|
package bimg
|
||||||
|
|
||||||
import (
|
import "io/ioutil"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Read(path string) ([]byte, error) {
|
func Read(path string) ([]byte, error) {
|
||||||
file, err := os.Open(path)
|
return ioutil.ReadFile(path)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
buf, err := ioutil.ReadAll(file)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Write(path string, buf []byte) error {
|
func Write(path string, buf []byte) error {
|
||||||
|
|
|
||||||
75
image.go
75
image.go
|
|
@ -4,6 +4,11 @@ type Image struct {
|
||||||
buffer []byte
|
buffer []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Creates a new image
|
||||||
|
func NewImage(buf []byte) *Image {
|
||||||
|
return &Image{buf}
|
||||||
|
}
|
||||||
|
|
||||||
// Resize the image to fixed width and height
|
// Resize the image to fixed width and height
|
||||||
func (i *Image) Resize(width, height int) ([]byte, error) {
|
func (i *Image) Resize(width, height int) ([]byte, error) {
|
||||||
options := Options{
|
options := Options{
|
||||||
|
|
@ -14,6 +19,27 @@ func (i *Image) Resize(width, height int) ([]byte, error) {
|
||||||
return i.Process(options)
|
return i.Process(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Force resize with custom size (aspect ratio won't be maintained)
|
||||||
|
func (i *Image) ForceResize(width, height int) ([]byte, error) {
|
||||||
|
options := Options{
|
||||||
|
Width: width,
|
||||||
|
Height: height,
|
||||||
|
Force: true,
|
||||||
|
}
|
||||||
|
return i.Process(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resize the image to fixed width and height with additional crop transformation
|
||||||
|
func (i *Image) ResizeAndCrop(width, height int) ([]byte, error) {
|
||||||
|
options := Options{
|
||||||
|
Width: width,
|
||||||
|
Height: height,
|
||||||
|
Embed: true,
|
||||||
|
Crop: true,
|
||||||
|
}
|
||||||
|
return i.Process(options)
|
||||||
|
}
|
||||||
|
|
||||||
// Extract area from the by X/Y axis
|
// Extract area from the by X/Y axis
|
||||||
func (i *Image) Extract(top, left, width, height int) ([]byte, error) {
|
func (i *Image) Extract(top, left, width, height int) ([]byte, error) {
|
||||||
options := Options{
|
options := Options{
|
||||||
|
|
@ -22,10 +48,15 @@ func (i *Image) Extract(top, left, width, height int) ([]byte, error) {
|
||||||
AreaWidth: width,
|
AreaWidth: width,
|
||||||
AreaHeight: height,
|
AreaHeight: height,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if top == 0 && left == 0 {
|
||||||
|
options.Top = -1
|
||||||
|
}
|
||||||
|
|
||||||
return i.Process(options)
|
return i.Process(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enlarge the image from the by X/Y axis
|
// Enlarge the image by width and height. Aspect ratio is maintained
|
||||||
func (i *Image) Enlarge(width, height int) ([]byte, error) {
|
func (i *Image) Enlarge(width, height int) ([]byte, error) {
|
||||||
options := Options{
|
options := Options{
|
||||||
Width: width,
|
Width: width,
|
||||||
|
|
@ -35,6 +66,17 @@ func (i *Image) Enlarge(width, height int) ([]byte, error) {
|
||||||
return i.Process(options)
|
return i.Process(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Enlarge the image by width and height with additional crop transformation
|
||||||
|
func (i *Image) EnlargeAndCrop(width, height int) ([]byte, error) {
|
||||||
|
options := Options{
|
||||||
|
Width: width,
|
||||||
|
Height: height,
|
||||||
|
Enlarge: true,
|
||||||
|
Crop: true,
|
||||||
|
}
|
||||||
|
return i.Process(options)
|
||||||
|
}
|
||||||
|
|
||||||
// Crop the image to the exact size specified
|
// Crop the image to the exact size specified
|
||||||
func (i *Image) Crop(width, height int, gravity Gravity) ([]byte, error) {
|
func (i *Image) Crop(width, height int, gravity Gravity) ([]byte, error) {
|
||||||
options := Options{
|
options := Options{
|
||||||
|
|
@ -75,15 +117,16 @@ func (i *Image) Thumbnail(pixels int) ([]byte, error) {
|
||||||
return i.Process(options)
|
return i.Process(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert an image to the existent one as watermark
|
// Add text as watermark on the given image
|
||||||
func (i *Image) Watermark(w Watermark) ([]byte, error) {
|
func (i *Image) Watermark(w Watermark) ([]byte, error) {
|
||||||
options := Options{Watermark: w}
|
options := Options{Watermark: w}
|
||||||
return i.Process(options)
|
return i.Process(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Zoom the image by the given factor
|
// Zoom the image by the given factor.
|
||||||
func (i *Image) Zoom(level int) ([]byte, error) {
|
// You should probably call Extract() before
|
||||||
options := Options{Zoom: level}
|
func (i *Image) Zoom(factor int) ([]byte, error) {
|
||||||
|
options := Options{Zoom: factor}
|
||||||
return i.Process(options)
|
return i.Process(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -111,6 +154,12 @@ func (i *Image) Convert(t ImageType) ([]byte, error) {
|
||||||
return i.Process(options)
|
return i.Process(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Colour space conversion
|
||||||
|
func (i *Image) Colourspace(c Interpretation) ([]byte, error) {
|
||||||
|
options := Options{Interpretation: c}
|
||||||
|
return i.Process(options)
|
||||||
|
}
|
||||||
|
|
||||||
// Transform the image by custom options
|
// Transform the image by custom options
|
||||||
func (i *Image) Process(o Options) ([]byte, error) {
|
func (i *Image) Process(o Options) ([]byte, error) {
|
||||||
image, err := Resize(i.buffer, o)
|
image, err := Resize(i.buffer, o)
|
||||||
|
|
@ -126,6 +175,17 @@ func (i *Image) Metadata() (ImageMetadata, error) {
|
||||||
return Metadata(i.buffer)
|
return Metadata(i.buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the image interpretation type
|
||||||
|
// See: http://www.vips.ecs.soton.ac.uk/supported/current/doc/html/libvips/VipsImage.html#VipsInterpretation
|
||||||
|
func (i *Image) Interpretation() (Interpretation, error) {
|
||||||
|
return ImageInterpretation(i.buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the current image has a valid colourspace
|
||||||
|
func (i *Image) ColourspaceIsSupported() (bool, error) {
|
||||||
|
return ColourspaceIsSupported(i.buffer)
|
||||||
|
}
|
||||||
|
|
||||||
// Get image type format (jpeg, png, webp, tiff)
|
// Get image type format (jpeg, png, webp, tiff)
|
||||||
func (i *Image) Type() string {
|
func (i *Image) Type() string {
|
||||||
return DetermineImageTypeName(i.buffer)
|
return DetermineImageTypeName(i.buffer)
|
||||||
|
|
@ -140,8 +200,3 @@ func (i *Image) Size() (ImageSize, error) {
|
||||||
func (i *Image) Image() []byte {
|
func (i *Image) Image() []byte {
|
||||||
return i.buffer
|
return i.buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates a new image
|
|
||||||
func NewImage(buf []byte) *Image {
|
|
||||||
return &Image{buf}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
157
image_test.go
157
image_test.go
|
|
@ -20,13 +20,27 @@ func TestImageResize(t *testing.T) {
|
||||||
Write("fixtures/test_resize_out.jpg", buf)
|
Write("fixtures/test_resize_out.jpg", buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestImageExtract(t *testing.T) {
|
func TestImageResizeAndCrop(t *testing.T) {
|
||||||
buf, err := initImage("test.jpg").Extract(100, 100, 300, 300)
|
buf, err := initImage("test.jpg").ResizeAndCrop(300, 200)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Cannot process the image: %#v", err)
|
t.Errorf("Cannot process the image: %#v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = assertSize(buf, 300, 300)
|
err = assertSize(buf, 300, 200)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
Write("fixtures/test_resize_crop_out.jpg", buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestImageExtract(t *testing.T) {
|
||||||
|
buf, err := initImage("test.jpg").Extract(100, 100, 300, 200)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Cannot process the image: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = assertSize(buf, 300, 200)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
@ -34,6 +48,20 @@ func TestImageExtract(t *testing.T) {
|
||||||
Write("fixtures/test_extract_out.jpg", buf)
|
Write("fixtures/test_extract_out.jpg", buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestImageExtractZero(t *testing.T) {
|
||||||
|
buf, err := initImage("test.jpg").Extract(0, 0, 300, 200)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Cannot process the image: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = assertSize(buf, 300, 200)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
Write("fixtures/test_extract_zero_out.jpg", buf)
|
||||||
|
}
|
||||||
|
|
||||||
func TestImageEnlarge(t *testing.T) {
|
func TestImageEnlarge(t *testing.T) {
|
||||||
buf, err := initImage("test.png").Enlarge(500, 375)
|
buf, err := initImage("test.png").Enlarge(500, 375)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -48,10 +76,24 @@ func TestImageEnlarge(t *testing.T) {
|
||||||
Write("fixtures/test_enlarge_out.jpg", buf)
|
Write("fixtures/test_enlarge_out.jpg", buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestImageEnlargeAndCrop(t *testing.T) {
|
||||||
|
buf, err := initImage("test.png").EnlargeAndCrop(800, 480)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Cannot process the image: %#v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = assertSize(buf, 800, 480)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
Write("fixtures/test_enlarge_crop_out.jpg", buf)
|
||||||
|
}
|
||||||
|
|
||||||
func TestImageCrop(t *testing.T) {
|
func TestImageCrop(t *testing.T) {
|
||||||
buf, err := initImage("test.jpg").Crop(800, 600, NORTH)
|
buf, err := initImage("test.jpg").Crop(800, 600, NORTH)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Cannot process the image: %#v", err)
|
t.Errorf("Cannot process the image: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = assertSize(buf, 800, 600)
|
err = assertSize(buf, 800, 600)
|
||||||
|
|
@ -65,7 +107,7 @@ func TestImageCrop(t *testing.T) {
|
||||||
func TestImageCropByWidth(t *testing.T) {
|
func TestImageCropByWidth(t *testing.T) {
|
||||||
buf, err := initImage("test.jpg").CropByWidth(600)
|
buf, err := initImage("test.jpg").CropByWidth(600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Cannot process the image: %#v", err)
|
t.Errorf("Cannot process the image: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = assertSize(buf, 600, 375)
|
err = assertSize(buf, 600, 375)
|
||||||
|
|
@ -79,7 +121,7 @@ func TestImageCropByWidth(t *testing.T) {
|
||||||
func TestImageCropByHeight(t *testing.T) {
|
func TestImageCropByHeight(t *testing.T) {
|
||||||
buf, err := initImage("test.jpg").CropByHeight(300)
|
buf, err := initImage("test.jpg").CropByHeight(300)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Cannot process the image: %#v", err)
|
t.Errorf("Cannot process the image: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = assertSize(buf, 480, 300)
|
err = assertSize(buf, 480, 300)
|
||||||
|
|
@ -93,7 +135,7 @@ func TestImageCropByHeight(t *testing.T) {
|
||||||
func TestImageThumbnail(t *testing.T) {
|
func TestImageThumbnail(t *testing.T) {
|
||||||
buf, err := initImage("test.jpg").Thumbnail(100)
|
buf, err := initImage("test.jpg").Thumbnail(100)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Cannot process the image: %#v", err)
|
t.Errorf("Cannot process the image: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = assertSize(buf, 100, 100)
|
err = assertSize(buf, 100, 100)
|
||||||
|
|
@ -134,13 +176,51 @@ func TestImageWatermark(t *testing.T) {
|
||||||
Write("fixtures/test_watermark_out.jpg", buf)
|
Write("fixtures/test_watermark_out.jpg", buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestImageZoom(t *testing.T) {
|
func TestImageWatermarkNoReplicate(t *testing.T) {
|
||||||
buf, err := initImage("test.jpg").Zoom(1)
|
image := initImage("test.jpg")
|
||||||
|
_, err := image.Crop(800, 600, NORTH)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Cannot process the image: %#v", err)
|
t.Errorf("Cannot process the image: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = assertSize(buf, 3360, 2100)
|
buf, err := image.Watermark(Watermark{
|
||||||
|
Text: "Copy me if you can",
|
||||||
|
Opacity: 0.5,
|
||||||
|
Width: 200,
|
||||||
|
DPI: 100,
|
||||||
|
NoReplicate: true,
|
||||||
|
Background: Color{255, 255, 255},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = assertSize(buf, 800, 600)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if DetermineImageType(buf) != JPEG {
|
||||||
|
t.Fatal("Image is not jpeg")
|
||||||
|
}
|
||||||
|
|
||||||
|
Write("fixtures/test_watermark_replicate_out.jpg", buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestImageZoom(t *testing.T) {
|
||||||
|
image := initImage("test.jpg")
|
||||||
|
|
||||||
|
_, err := image.Extract(100, 100, 400, 300)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Cannot extract the image: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := image.Zoom(1)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Cannot process the image: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = assertSize(buf, 800, 600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
@ -180,6 +260,19 @@ func TestImageConvert(t *testing.T) {
|
||||||
Write("fixtures/test_image_convert_out.png", buf)
|
Write("fixtures/test_image_convert_out.png", buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTransparentImageConvert(t *testing.T) {
|
||||||
|
image := initImage("transparent.png")
|
||||||
|
options := Options{
|
||||||
|
Type: JPEG,
|
||||||
|
Background: Color{255, 255, 255},
|
||||||
|
}
|
||||||
|
buf, err := image.Process(options)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Cannot process the image: %#v", err)
|
||||||
|
}
|
||||||
|
Write("fixtures/test_transparent_image_convert_out.jpg", buf)
|
||||||
|
}
|
||||||
|
|
||||||
func TestImageMetadata(t *testing.T) {
|
func TestImageMetadata(t *testing.T) {
|
||||||
data, err := initImage("test.png").Metadata()
|
data, err := initImage("test.png").Metadata()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -196,6 +289,48 @@ func TestImageMetadata(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestInterpretation(t *testing.T) {
|
||||||
|
interpretation, err := initImage("test.jpg").Interpretation()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Cannot process the image: %#v", err)
|
||||||
|
}
|
||||||
|
if interpretation != INTERPRETATION_sRGB {
|
||||||
|
t.Errorf("Invalid interpretation: %d", interpretation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestImageColourspace(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
file string
|
||||||
|
interpretation Interpretation
|
||||||
|
}{
|
||||||
|
{"test.jpg", INTERPRETATION_sRGB},
|
||||||
|
{"test.jpg", INTERPRETATION_B_W},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
buf, err := initImage(test.file).Colourspace(test.interpretation)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Cannot process the image: %#v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
interpretation, err := ImageInterpretation(buf)
|
||||||
|
if interpretation != test.interpretation {
|
||||||
|
t.Errorf("Invalid colourspace")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestImageColourspaceIsSupported(t *testing.T) {
|
||||||
|
supported, err := initImage("test.jpg").ColourspaceIsSupported()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Cannot process the image: %#v", err)
|
||||||
|
}
|
||||||
|
if supported != true {
|
||||||
|
t.Errorf("Non-supported colourspace")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestFluentInterface(t *testing.T) {
|
func TestFluentInterface(t *testing.T) {
|
||||||
image := initImage("test.jpg")
|
image := initImage("test.jpg")
|
||||||
_, err := image.CropByWidth(300)
|
_, err := image.CropByWidth(300)
|
||||||
|
|
|
||||||
14
metadata.go
14
metadata.go
|
|
@ -18,6 +18,7 @@ type ImageMetadata struct {
|
||||||
Profile bool
|
Profile bool
|
||||||
Type string
|
Type string
|
||||||
Space string
|
Space string
|
||||||
|
Colourspace string
|
||||||
Size ImageSize
|
Size ImageSize
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -34,6 +35,17 @@ func Size(buf []byte) (ImageSize, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check in the image colourspace is supported by libvips
|
||||||
|
func ColourspaceIsSupported(buf []byte) (bool, error) {
|
||||||
|
return vipsColourspaceIsSupportedBuffer(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the image interpretation type
|
||||||
|
// See: http://www.vips.ecs.soton.ac.uk/supported/current/doc/html/libvips/VipsImage.html#VipsInterpretation
|
||||||
|
func ImageInterpretation(buf []byte) (Interpretation, error) {
|
||||||
|
return vipsInterpretationBuffer(buf)
|
||||||
|
}
|
||||||
|
|
||||||
// Extract the image metadata (size, type, alpha channel, profile, EXIF orientation...)
|
// Extract the image metadata (size, type, alpha channel, profile, EXIF orientation...)
|
||||||
func Metadata(buf []byte) (ImageMetadata, error) {
|
func Metadata(buf []byte) (ImageMetadata, error) {
|
||||||
defer C.vips_thread_shutdown()
|
defer C.vips_thread_shutdown()
|
||||||
|
|
@ -56,7 +68,7 @@ func Metadata(buf []byte) (ImageMetadata, error) {
|
||||||
Alpha: vipsHasAlpha(image),
|
Alpha: vipsHasAlpha(image),
|
||||||
Profile: vipsHasProfile(image),
|
Profile: vipsHasProfile(image),
|
||||||
Space: vipsSpace(image),
|
Space: vipsSpace(image),
|
||||||
Type: getImageTypeName(imageType),
|
Type: ImageTypeName(imageType),
|
||||||
}
|
}
|
||||||
|
|
||||||
return metadata, nil
|
return metadata, nil
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ func TestSize(t *testing.T) {
|
||||||
{"test.png", 400, 300},
|
{"test.png", 400, 300},
|
||||||
{"test.webp", 550, 368},
|
{"test.webp", 550, 368},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
size, err := Size(readFile(file.name))
|
size, err := Size(readFile(file.name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -39,15 +38,15 @@ func TestMetadata(t *testing.T) {
|
||||||
profile bool
|
profile bool
|
||||||
space string
|
space string
|
||||||
}{
|
}{
|
||||||
{"test.jpg", "jpeg", 0, false, false, "bicubic"},
|
{"test.jpg", "jpeg", 0, false, false, "srgb"},
|
||||||
{"test.png", "png", 0, true, false, "bicubic"},
|
{"test.png", "png", 0, true, false, "srgb"},
|
||||||
{"test.webp", "webp", 0, false, false, "bicubic"},
|
{"test.webp", "webp", 0, false, false, "srgb"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
metadata, err := Metadata(readFile(file.name))
|
metadata, err := Metadata(readFile(file.name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Cannot read the image: %#v", err)
|
t.Fatalf("Cannot read the image: %s -> %s", file.name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if metadata.Type != file.format {
|
if metadata.Type != file.format {
|
||||||
|
|
@ -62,6 +61,58 @@ func TestMetadata(t *testing.T) {
|
||||||
if metadata.Profile != file.profile {
|
if metadata.Profile != file.profile {
|
||||||
t.Fatalf("Unexpected image profile: %s != %s", metadata.Profile, file.profile)
|
t.Fatalf("Unexpected image profile: %s != %s", metadata.Profile, file.profile)
|
||||||
}
|
}
|
||||||
|
if metadata.Space != file.space {
|
||||||
|
t.Fatalf("Unexpected image profile: %s != %s", metadata.Profile, file.profile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestImageInterpretation(t *testing.T) {
|
||||||
|
files := []struct {
|
||||||
|
name string
|
||||||
|
interpretation Interpretation
|
||||||
|
}{
|
||||||
|
{"test.jpg", INTERPRETATION_sRGB},
|
||||||
|
{"test.png", INTERPRETATION_sRGB},
|
||||||
|
{"test.webp", INTERPRETATION_sRGB},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
interpretation, err := ImageInterpretation(readFile(file.name))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Cannot read the image: %s -> %s", file.name, err)
|
||||||
|
}
|
||||||
|
if interpretation != file.interpretation {
|
||||||
|
t.Fatalf("Unexpected image interpretation")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestColourspaceIsSupported(t *testing.T) {
|
||||||
|
files := []struct {
|
||||||
|
name string
|
||||||
|
}{
|
||||||
|
{"test.jpg"},
|
||||||
|
{"test.png"},
|
||||||
|
{"test.webp"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
supported, err := ColourspaceIsSupported(readFile(file.name))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Cannot read the image: %s -> %s", file.name, err)
|
||||||
|
}
|
||||||
|
if supported != true {
|
||||||
|
t.Fatalf("Unsupported image colourspace")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
supported, err := initImage("test.jpg").ColourspaceIsSupported()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Cannot process the image: %#v", err)
|
||||||
|
}
|
||||||
|
if supported != true {
|
||||||
|
t.Errorf("Non-supported colourspace")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
99
options.go
99
options.go
|
|
@ -42,10 +42,10 @@ func (i Interpolator) String() string {
|
||||||
type Angle int
|
type Angle int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
D0 Angle = C.VIPS_ANGLE_D0
|
D0 Angle = 0
|
||||||
D90 Angle = C.VIPS_ANGLE_D90
|
D90 Angle = 90
|
||||||
D180 Angle = C.VIPS_ANGLE_D180
|
D180 Angle = 180
|
||||||
D270 Angle = C.VIPS_ANGLE_D270
|
D270 Angle = 270
|
||||||
)
|
)
|
||||||
|
|
||||||
type Direction int
|
type Direction int
|
||||||
|
|
@ -55,11 +55,35 @@ const (
|
||||||
VERTICAL Direction = C.VIPS_DIRECTION_VERTICAL
|
VERTICAL Direction = C.VIPS_DIRECTION_VERTICAL
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Image interpretation type
|
||||||
|
// See: http://www.vips.ecs.soton.ac.uk/supported/current/doc/html/libvips/VipsImage.html#VipsInterpretation
|
||||||
|
type Interpretation int
|
||||||
|
|
||||||
|
const (
|
||||||
|
INTERPRETATION_ERROR Interpretation = C.VIPS_INTERPRETATION_ERROR
|
||||||
|
INTERPRETATION_MULTIBAND Interpretation = C.VIPS_INTERPRETATION_MULTIBAND
|
||||||
|
INTERPRETATION_B_W Interpretation = C.VIPS_INTERPRETATION_B_W
|
||||||
|
INTERPRETATION_CMYK Interpretation = C.VIPS_INTERPRETATION_CMYK
|
||||||
|
INTERPRETATION_RGB Interpretation = C.VIPS_INTERPRETATION_RGB
|
||||||
|
INTERPRETATION_sRGB Interpretation = C.VIPS_INTERPRETATION_sRGB
|
||||||
|
INTERPRETATION_RGB16 Interpretation = C.VIPS_INTERPRETATION_RGB16
|
||||||
|
INTERPRETATION_GREY16 Interpretation = C.VIPS_INTERPRETATION_GREY16
|
||||||
|
INTERPRETATION_scRGB Interpretation = C.VIPS_INTERPRETATION_scRGB
|
||||||
|
INTERPRETATION_LAB Interpretation = C.VIPS_INTERPRETATION_LAB
|
||||||
|
INTERPRETATION_XYZ Interpretation = C.VIPS_INTERPRETATION_XYZ
|
||||||
|
)
|
||||||
|
|
||||||
|
const WATERMARK_FONT = "sans 10"
|
||||||
|
|
||||||
// Color represents a traditional RGB color scheme
|
// Color represents a traditional RGB color scheme
|
||||||
type Color struct {
|
type Color struct {
|
||||||
R, G, B uint8
|
R, G, B uint8
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Shortcut to black RGB color representation
|
||||||
|
var ColorBlack = Color{0, 0, 0}
|
||||||
|
|
||||||
|
// Text-based watermark configuration
|
||||||
type Watermark struct {
|
type Watermark struct {
|
||||||
Width int
|
Width int
|
||||||
DPI int
|
DPI int
|
||||||
|
|
@ -71,27 +95,48 @@ type Watermark struct {
|
||||||
Background Color
|
Background Color
|
||||||
}
|
}
|
||||||
|
|
||||||
type Options struct {
|
type GaussianBlur struct {
|
||||||
Height int
|
Sigma float64
|
||||||
Width int
|
MinAmpl float64
|
||||||
AreaHeight int
|
}
|
||||||
AreaWidth int
|
|
||||||
Top int
|
type Sharpen struct {
|
||||||
Left int
|
Radius int
|
||||||
Extend int
|
X1 float64
|
||||||
Quality int
|
Y2 float64
|
||||||
Compression int
|
Y3 float64
|
||||||
Zoom int
|
M1 float64
|
||||||
Crop bool
|
M2 float64
|
||||||
Enlarge bool
|
}
|
||||||
Embed bool
|
|
||||||
Flip bool
|
// Supported image transformation options
|
||||||
Flop bool
|
type Options struct {
|
||||||
NoAutoRotate bool
|
Height int
|
||||||
Colorspace bool
|
Width int
|
||||||
Rotate Angle
|
AreaHeight int
|
||||||
Gravity Gravity
|
AreaWidth int
|
||||||
Watermark Watermark
|
Top int
|
||||||
Type ImageType
|
Left int
|
||||||
Interpolator Interpolator
|
Extend int
|
||||||
|
Quality int
|
||||||
|
Compression int
|
||||||
|
Zoom int
|
||||||
|
Crop bool
|
||||||
|
Enlarge bool
|
||||||
|
Embed bool
|
||||||
|
Flip bool
|
||||||
|
Flop bool
|
||||||
|
Force bool
|
||||||
|
NoAutoRotate bool
|
||||||
|
NoProfile bool
|
||||||
|
Interlace bool
|
||||||
|
Rotate Angle
|
||||||
|
Background Color
|
||||||
|
Gravity Gravity
|
||||||
|
Watermark Watermark
|
||||||
|
Type ImageType
|
||||||
|
Interpolator Interpolator
|
||||||
|
Interpretation Interpretation
|
||||||
|
GaussianBlur GaussianBlur
|
||||||
|
Sharpen Sharpen
|
||||||
}
|
}
|
||||||
|
|
|
||||||
339
resize.go
339
resize.go
|
|
@ -23,16 +23,8 @@ func Resize(buf []byte, o Options) ([]byte, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Defaults
|
// Clone and define default options
|
||||||
if o.Quality == 0 {
|
o = applyDefaults(o, imageType)
|
||||||
o.Quality = QUALITY
|
|
||||||
}
|
|
||||||
if o.Compression == 0 {
|
|
||||||
o.Compression = 6
|
|
||||||
}
|
|
||||||
if o.Type == 0 {
|
|
||||||
o.Type = imageType
|
|
||||||
}
|
|
||||||
|
|
||||||
if IsTypeSupported(o.Type) == false {
|
if IsTypeSupported(o.Type) == false {
|
||||||
return nil, errors.New("Unsupported image output type")
|
return nil, errors.New("Unsupported image output type")
|
||||||
|
|
@ -43,13 +35,17 @@ func Resize(buf []byte, o Options) ([]byte, error) {
|
||||||
inWidth := int(image.Xsize)
|
inWidth := int(image.Xsize)
|
||||||
inHeight := int(image.Ysize)
|
inHeight := int(image.Ysize)
|
||||||
|
|
||||||
|
// Infer the required operation based on the in/out image sizes for a coherent transformation
|
||||||
|
normalizeOperation(&o, inWidth, inHeight)
|
||||||
|
|
||||||
// image calculations
|
// image calculations
|
||||||
factor := imageCalculations(&o, inWidth, inHeight)
|
factor := imageCalculations(&o, inWidth, inHeight)
|
||||||
shrink := int(math.Max(math.Floor(factor), 1))
|
shrink := calculateShrink(factor, o.Interpolator)
|
||||||
residual := float64(shrink) / factor
|
residual := calculateResidual(factor, shrink)
|
||||||
|
|
||||||
// Do not enlarge the output if the input width *or* height are already less than the required dimensions
|
// Do not enlarge the output if the input width or height
|
||||||
if o.Enlarge == false {
|
// are already less than the required dimensions
|
||||||
|
if !o.Enlarge && !o.Force {
|
||||||
if inWidth < o.Width && inHeight < o.Height {
|
if inWidth < o.Width && inHeight < o.Height {
|
||||||
factor = 1.0
|
factor = 1.0
|
||||||
shrink = 1
|
shrink = 1
|
||||||
|
|
@ -65,81 +61,161 @@ func Resize(buf []byte, o Options) ([]byte, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if tmpImage != nil {
|
|
||||||
image = tmpImage
|
image = tmpImage
|
||||||
factor = math.Max(factor, 1.0)
|
factor = math.Max(factor, 1.0)
|
||||||
shrink = int(math.Floor(factor))
|
shrink = int(math.Floor(factor))
|
||||||
residual = float64(shrink) / factor
|
residual = float64(shrink) / factor
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate integral box shrink
|
// Zoom image, if necessary
|
||||||
windowSize := vipsWindowSize(o.Interpolator.String())
|
|
||||||
if factor >= 2 && windowSize > 3 {
|
|
||||||
// Shrink less, affine more with interpolators that use at least 4x4 pixel window, e.g. bicubic
|
|
||||||
shrink = int(math.Max(float64(math.Floor(factor*3.0/windowSize)), 1))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transform image if necessary
|
|
||||||
shouldTransform := o.Width != inWidth || o.Height != inHeight || o.AreaWidth > 0 || o.AreaHeight > 0
|
|
||||||
if shouldTransform {
|
|
||||||
// Use vips_shrink with the integral reduction
|
|
||||||
if shrink > 1 {
|
|
||||||
image, residual, err = shrinkImage(image, o, residual, shrink)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Use vips_affine with the remaining float part
|
|
||||||
if residual != 0 {
|
|
||||||
image, err = vipsAffine(image, residual, o.Interpolator)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
debug("Transform image: factor=%v, shrink=%v, residual=%v", factor, shrink, residual)
|
|
||||||
// Extract area from image
|
|
||||||
image, err = extractImage(image, o)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Zoom image if necessary
|
|
||||||
image, err = zoomImage(image, o.Zoom)
|
image, err = zoomImage(image, o.Zoom)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rotate / flip image if necessary
|
// Rotate / flip image, if necessary
|
||||||
image, err = rotateImage(image, o)
|
image, err = rotateAndFlipImage(image, o)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
saveOptions := vipsSaveOptions{
|
// Transform image, if necessary
|
||||||
Quality: o.Quality,
|
if shouldTransformImage(o, inWidth, inHeight) {
|
||||||
Type: o.Type,
|
image, err = transformImage(image, o, shrink, residual)
|
||||||
Compression: o.Compression,
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// watermark
|
// Apply effects, if necessary
|
||||||
|
if shouldApplyEffects(o) {
|
||||||
|
image, err = applyEffects(image, o)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add watermark, if necessary
|
||||||
image, err = watermakImage(image, o.Watermark)
|
image, err = watermakImage(image, o.Watermark)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally save as buffer
|
// Flatten image on a background, if necessary
|
||||||
buf, err = vipsSave(image, saveOptions)
|
image, err = imageFlatten(image, imageType, o)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return buf, nil
|
saveOptions := vipsSaveOptions{
|
||||||
|
Quality: o.Quality,
|
||||||
|
Type: o.Type,
|
||||||
|
Compression: o.Compression,
|
||||||
|
Interlace: o.Interlace,
|
||||||
|
NoProfile: o.NoProfile,
|
||||||
|
Interpretation: o.Interpretation,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally get the resultant buffer
|
||||||
|
return vipsSave(image, saveOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractImage(image *C.struct__VipsImage, o Options) (*C.struct__VipsImage, error) {
|
func applyDefaults(o Options, imageType ImageType) Options {
|
||||||
|
if o.Quality == 0 {
|
||||||
|
o.Quality = QUALITY
|
||||||
|
}
|
||||||
|
if o.Compression == 0 {
|
||||||
|
o.Compression = 6
|
||||||
|
}
|
||||||
|
if o.Type == 0 {
|
||||||
|
o.Type = imageType
|
||||||
|
}
|
||||||
|
if o.Interpretation == 0 {
|
||||||
|
o.Interpretation = INTERPRETATION_sRGB
|
||||||
|
}
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeOperation(o *Options, inWidth, inHeight int) {
|
||||||
|
if !o.Force && !o.Crop && !o.Embed && !o.Enlarge && o.Rotate == 0 && (o.Width > 0 || o.Height > 0) {
|
||||||
|
o.Force = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func shouldTransformImage(o Options, inWidth, inHeight int) bool {
|
||||||
|
return o.Force || (o.Width > 0 && o.Width != inWidth) ||
|
||||||
|
(o.Height > 0 && o.Height != inHeight) || o.AreaWidth > 0 || o.AreaHeight > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func shouldApplyEffects(o Options) bool {
|
||||||
|
return o.GaussianBlur.Sigma > 0 || o.GaussianBlur.MinAmpl > 0 || o.Sharpen.Radius > 0 && o.Sharpen.Y2 > 0 || o.Sharpen.Y3 > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func transformImage(image *C.VipsImage, o Options, shrink int, residual float64) (*C.VipsImage, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Use vips_shrink with the integral reduction
|
||||||
|
if shrink > 1 {
|
||||||
|
image, residual, err = shrinkImage(image, o, residual, shrink)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
residualx, residualy := residual, residual
|
||||||
|
if o.Force {
|
||||||
|
residualx = float64(o.Width) / float64(image.Xsize)
|
||||||
|
residualy = float64(o.Height) / float64(image.Ysize)
|
||||||
|
}
|
||||||
|
|
||||||
|
if o.Force || residual != 0 {
|
||||||
|
image, err = vipsAffine(image, residualx, residualy, o.Interpolator)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if o.Force {
|
||||||
|
o.Crop = false
|
||||||
|
o.Embed = false
|
||||||
|
}
|
||||||
|
|
||||||
|
image, err = extractOrEmbedImage(image, o)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
debug("Transform: shrink=%v, residual=%v, interpolator=%v",
|
||||||
|
shrink, residual, o.Interpolator.String())
|
||||||
|
|
||||||
|
return image, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyEffects(image *C.VipsImage, o Options) (*C.VipsImage, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if o.GaussianBlur.Sigma > 0 || o.GaussianBlur.MinAmpl > 0 {
|
||||||
|
image, err = vipsGaussianBlur(image, o.GaussianBlur)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if o.Sharpen.Radius > 0 && o.Sharpen.Y2 > 0 || o.Sharpen.Y3 > 0 {
|
||||||
|
image, err = vipsSharpen(image, o.Sharpen)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debug("Effects: gaussSigma=%v, gaussMinAmpl=%v, sharpenRadius=%v",
|
||||||
|
o.GaussianBlur.Sigma, o.GaussianBlur.MinAmpl, o.Sharpen.Radius)
|
||||||
|
|
||||||
|
return image, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractOrEmbedImage(image *C.VipsImage, o Options) (*C.VipsImage, error) {
|
||||||
var err error = nil
|
var err error = nil
|
||||||
inWidth := int(image.Xsize)
|
inWidth := int(image.Xsize)
|
||||||
inHeight := int(image.Ysize)
|
inHeight := int(image.Ysize)
|
||||||
|
|
@ -149,25 +225,31 @@ func extractImage(image *C.struct__VipsImage, o Options) (*C.struct__VipsImage,
|
||||||
width := int(math.Min(float64(inWidth), float64(o.Width)))
|
width := int(math.Min(float64(inWidth), float64(o.Width)))
|
||||||
height := int(math.Min(float64(inHeight), float64(o.Height)))
|
height := int(math.Min(float64(inHeight), float64(o.Height)))
|
||||||
left, top := calculateCrop(inWidth, inHeight, o.Width, o.Height, o.Gravity)
|
left, top := calculateCrop(inWidth, inHeight, o.Width, o.Height, o.Gravity)
|
||||||
|
left, top = int(math.Max(float64(left), 0)), int(math.Max(float64(top), 0))
|
||||||
image, err = vipsExtract(image, left, top, width, height)
|
image, err = vipsExtract(image, left, top, width, height)
|
||||||
break
|
break
|
||||||
case o.Embed:
|
case o.Embed:
|
||||||
left, top := (o.Width-inWidth)/2, (o.Height-inHeight)/2
|
left, top := (o.Width-inWidth)/2, (o.Height-inHeight)/2
|
||||||
image, err = vipsEmbed(image, left, top, o.Width, o.Height, o.Extend)
|
image, err = vipsEmbed(image, left, top, o.Width, o.Height, o.Extend)
|
||||||
break
|
break
|
||||||
case o.Top > 0 || o.Left > 0:
|
case o.Top != 0 || o.Left != 0:
|
||||||
if o.AreaWidth == 0 || o.AreaHeight == 0 {
|
if o.AreaWidth == 0 {
|
||||||
err = errors.New("Area to extract cannot be 0")
|
o.AreaHeight = o.Width
|
||||||
} else {
|
|
||||||
image, err = vipsExtract(image, o.Left, o.Top, o.AreaWidth, o.AreaHeight)
|
|
||||||
}
|
}
|
||||||
|
if o.AreaHeight == 0 {
|
||||||
|
o.AreaHeight = o.Height
|
||||||
|
}
|
||||||
|
if o.AreaWidth == 0 || o.AreaHeight == 0 {
|
||||||
|
return nil, errors.New("Extract area width/height params are required")
|
||||||
|
}
|
||||||
|
image, err = vipsExtract(image, o.Left, o.Top, o.AreaWidth, o.AreaHeight)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
return image, err
|
return image, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func rotateImage(image *C.struct__VipsImage, o Options) (*C.struct__VipsImage, error) {
|
func rotateAndFlipImage(image *C.VipsImage, o Options) (*C.VipsImage, error) {
|
||||||
var err error
|
var err error
|
||||||
var direction Direction = -1
|
var direction Direction = -1
|
||||||
|
|
||||||
|
|
@ -198,17 +280,17 @@ func rotateImage(image *C.struct__VipsImage, o Options) (*C.struct__VipsImage, e
|
||||||
return image, err
|
return image, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func watermakImage(image *C.struct__VipsImage, w Watermark) (*C.struct__VipsImage, error) {
|
func watermakImage(image *C.VipsImage, w Watermark) (*C.VipsImage, error) {
|
||||||
if len(w.Text) == 0 {
|
if w.Text == "" {
|
||||||
return image, nil
|
return image, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Defaults
|
// Defaults
|
||||||
if len(w.Font) == 0 {
|
if w.Font == "" {
|
||||||
w.Font = "sans 10"
|
w.Font = WATERMARK_FONT
|
||||||
}
|
}
|
||||||
if w.Width == 0 {
|
if w.Width == 0 {
|
||||||
w.Width = int(math.Floor(float64(image.Xsize / 8)))
|
w.Width = int(math.Floor(float64(image.Xsize / 6)))
|
||||||
}
|
}
|
||||||
if w.DPI == 0 {
|
if w.DPI == 0 {
|
||||||
w.DPI = 150
|
w.DPI = 150
|
||||||
|
|
@ -230,15 +312,23 @@ func watermakImage(image *C.struct__VipsImage, w Watermark) (*C.struct__VipsImag
|
||||||
return image, nil
|
return image, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func zoomImage(image *C.struct__VipsImage, zoom int) (*C.struct__VipsImage, error) {
|
func imageFlatten(image *C.VipsImage, imageType ImageType, o Options) (*C.VipsImage, error) {
|
||||||
if zoom == 0 {
|
// Only PNG images are supported for now
|
||||||
|
if imageType != PNG || o.Background == ColorBlack {
|
||||||
return image, nil
|
return image, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return vipsFlattenBackground(image, o.Background)
|
||||||
|
}
|
||||||
|
|
||||||
|
func zoomImage(image *C.VipsImage, zoom int) (*C.VipsImage, error) {
|
||||||
|
if zoom == 0 {
|
||||||
|
return image, nil
|
||||||
|
}
|
||||||
return vipsZoom(image, zoom+1)
|
return vipsZoom(image, zoom+1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func shrinkImage(image *C.struct__VipsImage, o Options, residual float64, shrink int) (*C.struct__VipsImage, float64, error) {
|
func shrinkImage(image *C.VipsImage, o Options, residual float64, shrink int) (*C.VipsImage, float64, error) {
|
||||||
// Use vips_shrink with the integral reduction
|
// Use vips_shrink with the integral reduction
|
||||||
image, err := vipsShrink(image, shrink)
|
image, err := vipsShrink(image, shrink)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -258,8 +348,8 @@ func shrinkImage(image *C.struct__VipsImage, o Options, residual float64, shrink
|
||||||
return image, residual, nil
|
return image, residual, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func shrinkJpegImage(buf []byte, input *C.struct__VipsImage, factor float64, shrink int) (*C.struct__VipsImage, float64, error) {
|
func shrinkJpegImage(buf []byte, input *C.VipsImage, factor float64, shrink int) (*C.VipsImage, float64, error) {
|
||||||
var image *C.struct__VipsImage
|
var image *C.VipsImage
|
||||||
var err error
|
var err error
|
||||||
shrinkOnLoad := 1
|
shrinkOnLoad := 1
|
||||||
|
|
||||||
|
|
@ -305,8 +395,8 @@ func imageCalculations(o *Options, inWidth, inHeight int) float64 {
|
||||||
case o.Height > 0:
|
case o.Height > 0:
|
||||||
factor = yfactor
|
factor = yfactor
|
||||||
o.Width = int(math.Floor(float64(inWidth) / factor))
|
o.Width = int(math.Floor(float64(inWidth) / factor))
|
||||||
|
// Identity transform
|
||||||
default:
|
default:
|
||||||
// Identity transform
|
|
||||||
o.Width = inWidth
|
o.Width = inWidth
|
||||||
o.Height = inHeight
|
o.Height = inHeight
|
||||||
break
|
break
|
||||||
|
|
@ -337,50 +427,63 @@ func calculateCrop(inWidth, inHeight, outWidth, outHeight int, gravity Gravity)
|
||||||
return left, top
|
return left, top
|
||||||
}
|
}
|
||||||
|
|
||||||
func calculateRotationAndFlip(image *C.struct__VipsImage, angle Angle) (Angle, bool) {
|
func calculateRotationAndFlip(image *C.VipsImage, angle Angle) (Angle, bool) {
|
||||||
rotate := D0
|
rotate := D0
|
||||||
flip := false
|
flip := false
|
||||||
|
|
||||||
if angle == -1 {
|
if angle > 0 {
|
||||||
switch vipsExifOrientation(image) {
|
return rotate, flip
|
||||||
case 6:
|
}
|
||||||
rotate = D90
|
|
||||||
break
|
switch vipsExifOrientation(image) {
|
||||||
case 3:
|
case 6:
|
||||||
rotate = D180
|
rotate = D90
|
||||||
break
|
break
|
||||||
case 8:
|
case 3:
|
||||||
rotate = D270
|
rotate = D180
|
||||||
break
|
break
|
||||||
case 2:
|
case 8:
|
||||||
flip = true
|
rotate = D270
|
||||||
break // flip 1
|
break
|
||||||
case 7:
|
case 2:
|
||||||
flip = true
|
flip = true
|
||||||
rotate = D90
|
break // flip 1
|
||||||
break // flip 6
|
case 7:
|
||||||
case 4:
|
flip = true
|
||||||
flip = true
|
rotate = D90
|
||||||
rotate = D180
|
break // flip 6
|
||||||
break // flip 3
|
case 4:
|
||||||
case 5:
|
flip = true
|
||||||
flip = true
|
rotate = D180
|
||||||
rotate = D270
|
break // flip 3
|
||||||
break // flip 8
|
case 5:
|
||||||
}
|
flip = true
|
||||||
} else {
|
rotate = D270
|
||||||
if angle == 90 {
|
break // flip 8
|
||||||
rotate = D90
|
|
||||||
} else if angle == 180 {
|
|
||||||
rotate = D180
|
|
||||||
} else if angle == 270 {
|
|
||||||
rotate = D270
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return rotate, flip
|
return rotate, flip
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func calculateShrink(factor float64, i Interpolator) int {
|
||||||
|
var shrink float64
|
||||||
|
|
||||||
|
// Calculate integral box shrink
|
||||||
|
windowSize := vipsWindowSize(i.String())
|
||||||
|
if factor >= 2 && windowSize > 3 {
|
||||||
|
// Shrink less, affine more with interpolators that use at least 4x4 pixel window, e.g. bicubic
|
||||||
|
shrink = float64(math.Floor(factor * 3.0 / windowSize))
|
||||||
|
} else {
|
||||||
|
shrink = math.Floor(factor)
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(math.Max(shrink, 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
func calculateResidual(factor float64, shrink int) float64 {
|
||||||
|
return float64(shrink) / factor
|
||||||
|
}
|
||||||
|
|
||||||
func getAngle(angle Angle) Angle {
|
func getAngle(angle Angle) Angle {
|
||||||
divisor := angle % 90
|
divisor := angle % 90
|
||||||
if divisor != 0 {
|
if divisor != 0 {
|
||||||
|
|
|
||||||
340
resize_test.go
340
resize_test.go
|
|
@ -20,9 +20,51 @@ func TestResize(t *testing.T) {
|
||||||
t.Fatal("Image is not jpeg")
|
t.Fatal("Image is not jpeg")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = Write("fixtures/test_out.jpg", newImg)
|
size, _ := Size(newImg)
|
||||||
if err != nil {
|
if size.Height != options.Height || size.Width != options.Width {
|
||||||
t.Fatal("Cannot save the image")
|
t.Fatalf("Invalid image size: %dx%d", size.Width, size.Height)
|
||||||
|
}
|
||||||
|
|
||||||
|
Write("fixtures/test_out.jpg", newImg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResizeCustomSizes(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
file string
|
||||||
|
format ImageType
|
||||||
|
options Options
|
||||||
|
}{
|
||||||
|
{"test.jpg", JPEG, Options{Width: 800, Height: 600}},
|
||||||
|
{"test.jpg", JPEG, Options{Width: 1000, Height: 1000}},
|
||||||
|
{"test.jpg", JPEG, Options{Width: 100, Height: 50}},
|
||||||
|
{"test.jpg", JPEG, Options{Width: 2000, Height: 2000}},
|
||||||
|
{"test.jpg", JPEG, Options{Width: 500, Height: 1000}},
|
||||||
|
{"test.jpg", JPEG, Options{Width: 500}},
|
||||||
|
{"test.jpg", JPEG, Options{Height: 500}},
|
||||||
|
{"test.jpg", JPEG, Options{Crop: true, Width: 500, Height: 1000}},
|
||||||
|
{"test.jpg", JPEG, Options{Crop: true, Enlarge: true, Width: 2000, Height: 1400}},
|
||||||
|
{"test.jpg", JPEG, Options{Enlarge: true, Force: true, Width: 2000, Height: 2000}},
|
||||||
|
{"test.jpg", JPEG, Options{Force: true, Width: 2000, Height: 2000}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
buf, _ := Read("fixtures/" + test.file)
|
||||||
|
image, err := Resize(buf, test.options)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Resize(imgData, %#v) error: %#v", test.options, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if DetermineImageType(image) != test.format {
|
||||||
|
t.Fatal("Image format is invalid. Expected: %s", test.format)
|
||||||
|
}
|
||||||
|
|
||||||
|
size, _ := Size(image)
|
||||||
|
if test.options.Height > 0 && size.Height != test.options.Height {
|
||||||
|
t.Fatalf("Invalid height: %d", size.Height)
|
||||||
|
}
|
||||||
|
if test.options.Width > 0 && size.Width != test.options.Width {
|
||||||
|
t.Fatalf("Invalid width: %d", size.Width)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -39,48 +81,12 @@ func TestRotate(t *testing.T) {
|
||||||
t.Fatal("Image is not jpeg")
|
t.Fatal("Image is not jpeg")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = Write("fixtures/test_rotate_out.jpg", newImg)
|
size, _ := Size(newImg)
|
||||||
if err != nil {
|
if size.Height != options.Width {
|
||||||
t.Fatal("Cannot save the image")
|
t.Fatalf("Invalid image size: %dx%d", size.Width, size.Height)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testColorspace(t *testing.T) {
|
|
||||||
options := Options{Colorspace: true}
|
|
||||||
buf, _ := Read("fixtures/sky.jpg")
|
|
||||||
|
|
||||||
newImg, err := Resize(buf, options)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Resize(imgData, %#v) error: %#v", options, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if DetermineImageType(newImg) != JPEG {
|
Write("fixtures/test_rotate_out.jpg", newImg)
|
||||||
t.Fatal("Image is not jpeg")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = Write("fixtures/test_color_out.jpg", newImg)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Cannot save the image")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCorruptedImage(t *testing.T) {
|
|
||||||
options := Options{Width: 800, Height: 600}
|
|
||||||
buf, _ := Read("fixtures/corrupt.jpg")
|
|
||||||
|
|
||||||
newImg, err := Resize(buf, options)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Resize(imgData, %#v) error: %#v", options, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if DetermineImageType(newImg) != JPEG {
|
|
||||||
t.Fatal("Image is not jpeg")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = Write("fixtures/test_corrupt_out.jpg", newImg)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Cannot save the image")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInvalidRotate(t *testing.T) {
|
func TestInvalidRotate(t *testing.T) {
|
||||||
|
|
@ -96,44 +102,128 @@ func TestInvalidRotate(t *testing.T) {
|
||||||
t.Fatal("Image is not jpeg")
|
t.Fatal("Image is not jpeg")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = Write("fixtures/test_invalid_rotate_out.jpg", newImg)
|
size, _ := Size(newImg)
|
||||||
if err != nil {
|
if size.Height != options.Width {
|
||||||
t.Fatal("Cannot save the image")
|
t.Fatalf("Invalid image size: %dx%d", size.Width, size.Height)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Write("fixtures/test_invalid_rotate_out.jpg", newImg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConvert(t *testing.T) {
|
func TestCorruptedImage(t *testing.T) {
|
||||||
width, height := 640, 480
|
options := Options{Width: 800, Height: 600}
|
||||||
|
buf, _ := Read("fixtures/corrupt.jpg")
|
||||||
options := Options{Width: width, Height: height, Crop: true, Type: PNG}
|
|
||||||
img, err := os.Open("fixtures/test.jpg")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer img.Close()
|
|
||||||
|
|
||||||
buf, err := ioutil.ReadAll(img)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
newImg, err := Resize(buf, options)
|
newImg, err := Resize(buf, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Resize(imgData, %#v) error: %#v", options, err)
|
t.Errorf("Resize(imgData, %#v) error: %#v", options, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if DetermineImageType(newImg) != PNG {
|
if DetermineImageType(newImg) != JPEG {
|
||||||
t.Fatal("Image is not png")
|
t.Fatal("Image is not jpeg")
|
||||||
}
|
}
|
||||||
|
|
||||||
size, _ := Size(newImg)
|
size, _ := Size(newImg)
|
||||||
if size.Height != height || size.Width != width {
|
if size.Height != options.Height || size.Width != options.Width {
|
||||||
t.Fatal("Invalid image size")
|
t.Fatalf("Invalid image size: %dx%d", size.Width, size.Height)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = Write("fixtures/test_out.png", newImg)
|
Write("fixtures/test_corrupt_out.jpg", newImg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoColorProfile(t *testing.T) {
|
||||||
|
options := Options{Width: 800, Height: 600, NoProfile: true}
|
||||||
|
buf, _ := Read("fixtures/test.jpg")
|
||||||
|
|
||||||
|
newImg, err := Resize(buf, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Cannot save the image")
|
t.Errorf("Resize(imgData, %#v) error: %#v", options, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata, err := Metadata(newImg)
|
||||||
|
if metadata.Profile == true {
|
||||||
|
t.Fatal("Invalid profile data")
|
||||||
|
}
|
||||||
|
|
||||||
|
size, _ := Size(newImg)
|
||||||
|
if size.Height != options.Height || size.Width != options.Width {
|
||||||
|
t.Fatalf("Invalid image size: %dx%d", size.Width, size.Height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGaussianBlur(t *testing.T) {
|
||||||
|
options := Options{Width: 800, Height: 600, GaussianBlur: GaussianBlur{Sigma: 5}}
|
||||||
|
buf, _ := Read("fixtures/test.jpg")
|
||||||
|
|
||||||
|
newImg, err := Resize(buf, options)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Resize(imgData, %#v) error: %#v", options, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
size, _ := Size(newImg)
|
||||||
|
if size.Height != options.Height || size.Width != options.Width {
|
||||||
|
t.Fatalf("Invalid image size: %dx%d", size.Width, size.Height)
|
||||||
|
}
|
||||||
|
|
||||||
|
Write("fixtures/test_gaussian.jpg", newImg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSharpen(t *testing.T) {
|
||||||
|
options := Options{Width: 800, Height: 600, Sharpen: Sharpen{Radius: 1, X1: 1.5, Y2: 20, Y3: 50, M1: 1, M2: 2}}
|
||||||
|
buf, _ := Read("fixtures/test.jpg")
|
||||||
|
|
||||||
|
newImg, err := Resize(buf, options)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Resize(imgData, %#v) error: %#v", options, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
size, _ := Size(newImg)
|
||||||
|
if size.Height != options.Height || size.Width != options.Width {
|
||||||
|
t.Fatalf("Invalid image size: %dx%d", size.Width, size.Height)
|
||||||
|
}
|
||||||
|
|
||||||
|
Write("fixtures/test_sharpen.jpg", newImg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConvert(t *testing.T) {
|
||||||
|
width, height := 300, 240
|
||||||
|
formats := [3]ImageType{PNG, WEBP, JPEG}
|
||||||
|
|
||||||
|
files := []string{
|
||||||
|
"test.jpg",
|
||||||
|
"test.png",
|
||||||
|
"test.webp",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
img, err := os.Open("fixtures/" + file)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(img)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
img.Close()
|
||||||
|
|
||||||
|
for _, format := range formats {
|
||||||
|
options := Options{Width: width, Height: height, Crop: true, Type: format}
|
||||||
|
|
||||||
|
newImg, err := Resize(buf, options)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Resize(imgData, %#v) error: %#v", options, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if DetermineImageType(newImg) != format {
|
||||||
|
t.Fatal("Image is not png")
|
||||||
|
}
|
||||||
|
|
||||||
|
size, _ := Size(newImg)
|
||||||
|
if size.Height != height || size.Width != width {
|
||||||
|
t.Fatalf("Invalid image size: %dx%d", size.Width, size.Height)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -166,10 +256,7 @@ func TestResizePngWithTransparency(t *testing.T) {
|
||||||
t.Fatal("Invalid image size")
|
t.Fatal("Invalid image size")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = Write("fixtures/transparent_out.png", newImg)
|
Write("fixtures/transparent_out.png", newImg)
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Cannot save the image")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func runBenchmarkResize(file string, o Options, b *testing.B) {
|
func runBenchmarkResize(file string, o Options, b *testing.B) {
|
||||||
|
|
@ -180,6 +267,11 @@ func runBenchmarkResize(file string, o Options, b *testing.B) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkRotateJpeg(b *testing.B) {
|
||||||
|
options := Options{Rotate: 180}
|
||||||
|
runBenchmarkResize("test.jpg", options, b)
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkResizeLargeJpeg(b *testing.B) {
|
func BenchmarkResizeLargeJpeg(b *testing.B) {
|
||||||
options := Options{
|
options := Options{
|
||||||
Width: 800,
|
Width: 800,
|
||||||
|
|
@ -209,7 +301,17 @@ func BenchmarkConvertToJpeg(b *testing.B) {
|
||||||
runBenchmarkResize("test.png", options, b)
|
runBenchmarkResize("test.png", options, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkCrop(b *testing.B) {
|
func BenchmarkConvertToPng(b *testing.B) {
|
||||||
|
options := Options{Type: PNG}
|
||||||
|
runBenchmarkResize("test.jpg", options, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkConvertToWebp(b *testing.B) {
|
||||||
|
options := Options{Type: WEBP}
|
||||||
|
runBenchmarkResize("test.jpg", options, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkCropJpeg(b *testing.B) {
|
||||||
options := Options{
|
options := Options{
|
||||||
Width: 800,
|
Width: 800,
|
||||||
Height: 600,
|
Height: 600,
|
||||||
|
|
@ -217,6 +319,22 @@ func BenchmarkCrop(b *testing.B) {
|
||||||
runBenchmarkResize("test.jpg", options, b)
|
runBenchmarkResize("test.jpg", options, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkCropPng(b *testing.B) {
|
||||||
|
options := Options{
|
||||||
|
Width: 800,
|
||||||
|
Height: 600,
|
||||||
|
}
|
||||||
|
runBenchmarkResize("test.png", options, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkCropWebP(b *testing.B) {
|
||||||
|
options := Options{
|
||||||
|
Width: 800,
|
||||||
|
Height: 600,
|
||||||
|
}
|
||||||
|
runBenchmarkResize("test.webp", options, b)
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkExtractJpeg(b *testing.B) {
|
func BenchmarkExtractJpeg(b *testing.B) {
|
||||||
options := Options{
|
options := Options{
|
||||||
Top: 100,
|
Top: 100,
|
||||||
|
|
@ -226,3 +344,83 @@ func BenchmarkExtractJpeg(b *testing.B) {
|
||||||
}
|
}
|
||||||
runBenchmarkResize("test.jpg", options, b)
|
runBenchmarkResize("test.jpg", options, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkExtractPng(b *testing.B) {
|
||||||
|
options := Options{
|
||||||
|
Top: 100,
|
||||||
|
Left: 50,
|
||||||
|
AreaWidth: 600,
|
||||||
|
AreaHeight: 480,
|
||||||
|
}
|
||||||
|
runBenchmarkResize("test.png", options, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkExtractWebp(b *testing.B) {
|
||||||
|
options := Options{
|
||||||
|
Top: 100,
|
||||||
|
Left: 50,
|
||||||
|
AreaWidth: 600,
|
||||||
|
AreaHeight: 480,
|
||||||
|
}
|
||||||
|
runBenchmarkResize("test.webp", options, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkZoomJpeg(b *testing.B) {
|
||||||
|
options := Options{Zoom: 1}
|
||||||
|
runBenchmarkResize("test.jpg", options, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkZoomPng(b *testing.B) {
|
||||||
|
options := Options{Zoom: 1}
|
||||||
|
runBenchmarkResize("test.png", options, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkZoomWebp(b *testing.B) {
|
||||||
|
options := Options{Zoom: 1}
|
||||||
|
runBenchmarkResize("test.webp", options, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkWatermarkJpeg(b *testing.B) {
|
||||||
|
options := Options{
|
||||||
|
Watermark: Watermark{
|
||||||
|
Text: "Chuck Norris (c) 2315",
|
||||||
|
Opacity: 0.25,
|
||||||
|
Width: 200,
|
||||||
|
DPI: 100,
|
||||||
|
Margin: 150,
|
||||||
|
Font: "sans bold 12",
|
||||||
|
Background: Color{255, 255, 255},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
runBenchmarkResize("test.jpg", options, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkWatermarPng(b *testing.B) {
|
||||||
|
options := Options{
|
||||||
|
Watermark: Watermark{
|
||||||
|
Text: "Chuck Norris (c) 2315",
|
||||||
|
Opacity: 0.25,
|
||||||
|
Width: 200,
|
||||||
|
DPI: 100,
|
||||||
|
Margin: 150,
|
||||||
|
Font: "sans bold 12",
|
||||||
|
Background: Color{255, 255, 255},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
runBenchmarkResize("test.png", options, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkWatermarWebp(b *testing.B) {
|
||||||
|
options := Options{
|
||||||
|
Watermark: Watermark{
|
||||||
|
Text: "Chuck Norris (c) 2315",
|
||||||
|
Opacity: 0.25,
|
||||||
|
Width: 200,
|
||||||
|
DPI: 100,
|
||||||
|
Margin: 150,
|
||||||
|
Font: "sans bold 12",
|
||||||
|
Background: Color{255, 255, 255},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
runBenchmarkResize("test.webp", options, b)
|
||||||
|
}
|
||||||
|
|
|
||||||
45
type.go
45
type.go
|
|
@ -11,6 +11,15 @@ const (
|
||||||
MAGICK
|
MAGICK
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Pairs of image type and its name
|
||||||
|
var ImageTypes = map[ImageType]string{
|
||||||
|
JPEG: "jpeg",
|
||||||
|
PNG: "png",
|
||||||
|
WEBP: "webp",
|
||||||
|
TIFF: "tiff",
|
||||||
|
MAGICK: "magick",
|
||||||
|
}
|
||||||
|
|
||||||
// Determines the image type format (jpeg, png, webp or tiff)
|
// Determines the image type format (jpeg, png, webp or tiff)
|
||||||
func DetermineImageType(buf []byte) ImageType {
|
func DetermineImageType(buf []byte) ImageType {
|
||||||
return vipsImageType(buf)
|
return vipsImageType(buf)
|
||||||
|
|
@ -18,40 +27,28 @@ func DetermineImageType(buf []byte) ImageType {
|
||||||
|
|
||||||
// Determines the image type format by name (jpeg, png, webp or tiff)
|
// Determines the image type format by name (jpeg, png, webp or tiff)
|
||||||
func DetermineImageTypeName(buf []byte) string {
|
func DetermineImageTypeName(buf []byte) string {
|
||||||
return getImageTypeName(vipsImageType(buf))
|
return ImageTypeName(vipsImageType(buf))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if a given image type is supported
|
// Check if a given image type is supported
|
||||||
func IsTypeSupported(t ImageType) bool {
|
func IsTypeSupported(t ImageType) bool {
|
||||||
return t == JPEG || t == PNG || t == WEBP
|
return ImageTypes[t] != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if a given image type name is supported
|
// Check if a given image type name is supported
|
||||||
func IsTypeNameSupported(t string) bool {
|
func IsTypeNameSupported(t string) bool {
|
||||||
return t == "jpeg" || t == "jpg" ||
|
for _, name := range ImageTypes {
|
||||||
t == "png" || t == "webp"
|
if name == t {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func getImageTypeName(code ImageType) string {
|
func ImageTypeName(t ImageType) string {
|
||||||
imageType := "unknown"
|
imageType := ImageTypes[t]
|
||||||
|
if imageType == "" {
|
||||||
switch {
|
return "unknown"
|
||||||
case code == JPEG:
|
|
||||||
imageType = "jpeg"
|
|
||||||
break
|
|
||||||
case code == WEBP:
|
|
||||||
imageType = "webp"
|
|
||||||
break
|
|
||||||
case code == PNG:
|
|
||||||
imageType = "png"
|
|
||||||
break
|
|
||||||
case code == TIFF:
|
|
||||||
imageType = "tiff"
|
|
||||||
break
|
|
||||||
case code == MAGICK:
|
|
||||||
imageType = "magick"
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return imageType
|
return imageType
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ func TestIsTypeNameSupported(t *testing.T) {
|
||||||
name string
|
name string
|
||||||
expected bool
|
expected bool
|
||||||
}{
|
}{
|
||||||
{"jpg", true},
|
{"jpeg", true},
|
||||||
{"png", true},
|
{"png", true},
|
||||||
{"webp", true},
|
{"webp", true},
|
||||||
{"gif", false},
|
{"gif", false},
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
package bimg
|
package bimg
|
||||||
|
|
||||||
const Version = "0.1.5"
|
const Version = "0.1.21"
|
||||||
|
|
|
||||||
341
vips.go
341
vips.go
|
|
@ -3,21 +3,32 @@ package bimg
|
||||||
/*
|
/*
|
||||||
#cgo pkg-config: vips
|
#cgo pkg-config: vips
|
||||||
#include "vips.h"
|
#include "vips.h"
|
||||||
#include "stdlib.h"
|
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Current libvips version
|
||||||
|
const VipsVersion = string(C.VIPS_VERSION)
|
||||||
|
|
||||||
|
const HasMagickSupport = int(C.VIPS_MAGICK_SUPPORT) == 1
|
||||||
|
|
||||||
|
const (
|
||||||
|
maxCacheMem = 100 * 1024 * 1024
|
||||||
|
maxCacheSize = 500
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
m sync.Mutex
|
m sync.Mutex
|
||||||
initialized bool = false
|
initialized bool
|
||||||
)
|
)
|
||||||
|
|
||||||
type VipsMemoryInfo struct {
|
type VipsMemoryInfo struct {
|
||||||
|
|
@ -27,9 +38,12 @@ type VipsMemoryInfo struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type vipsSaveOptions struct {
|
type vipsSaveOptions struct {
|
||||||
Quality int
|
Quality int
|
||||||
Compression int
|
Compression int
|
||||||
Type ImageType
|
Type ImageType
|
||||||
|
Interlace bool
|
||||||
|
NoProfile bool
|
||||||
|
Interpretation Interpretation
|
||||||
}
|
}
|
||||||
|
|
||||||
type vipsWatermarkOptions struct {
|
type vipsWatermarkOptions struct {
|
||||||
|
|
@ -47,16 +61,16 @@ type vipsWatermarkTextOptions struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
if C.VIPS_MAJOR_VERSION <= 7 && C.VIPS_MINOR_VERSION < 40 {
|
|
||||||
panic("unsupported old vips version!")
|
|
||||||
}
|
|
||||||
|
|
||||||
Initialize()
|
Initialize()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Explicit thread-safe start of libvips.
|
// Explicit thread-safe start of libvips.
|
||||||
// Only call this function if you've previously shutdown libvips
|
// Only call this function if you've previously shutdown libvips
|
||||||
func Initialize() {
|
func Initialize() {
|
||||||
|
if C.VIPS_MAJOR_VERSION <= 7 && C.VIPS_MINOR_VERSION < 40 {
|
||||||
|
panic("unsupported libvips version!")
|
||||||
|
}
|
||||||
|
|
||||||
m.Lock()
|
m.Lock()
|
||||||
runtime.LockOSThread()
|
runtime.LockOSThread()
|
||||||
defer m.Unlock()
|
defer m.Unlock()
|
||||||
|
|
@ -64,30 +78,42 @@ func Initialize() {
|
||||||
|
|
||||||
err := C.vips_init(C.CString("bimg"))
|
err := C.vips_init(C.CString("bimg"))
|
||||||
if err != 0 {
|
if err != 0 {
|
||||||
Shutdown()
|
|
||||||
panic("unable to start vips!")
|
panic("unable to start vips!")
|
||||||
}
|
}
|
||||||
|
|
||||||
C.vips_concurrency_set(0) // default
|
// Set libvips cache params
|
||||||
C.vips_cache_set_max_mem(100 * 1024 * 1024) // 100 MB
|
C.vips_cache_set_max_mem(maxCacheMem)
|
||||||
C.vips_cache_set_max(500) // 500 operations
|
C.vips_cache_set_max(maxCacheSize)
|
||||||
|
|
||||||
|
// Define a custom thread concurrency limit in libvips (this may generate thread-unsafe issues)
|
||||||
|
// See: https://github.com/jcupitt/libvips/issues/261#issuecomment-92850414
|
||||||
|
if os.Getenv("VIPS_CONCURRENCY") == "" {
|
||||||
|
C.vips_concurrency_set(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable libvips cache tracing
|
||||||
|
if os.Getenv("VIPS_TRACE") != "" {
|
||||||
|
C.vips_enable_cache_set_trace()
|
||||||
|
}
|
||||||
|
|
||||||
initialized = true
|
initialized = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Explicit thread-safe libvips shutdown. Call this to drop caches.
|
// Thread-safe function to shutdown libvips.
|
||||||
|
// You can call this to drop caches as well.
|
||||||
// If libvips was already initialized, the function is no-op
|
// If libvips was already initialized, the function is no-op
|
||||||
func Shutdown() {
|
func Shutdown() {
|
||||||
m.Lock()
|
m.Lock()
|
||||||
defer m.Unlock()
|
defer m.Unlock()
|
||||||
|
|
||||||
if initialized == true {
|
if initialized {
|
||||||
C.vips_shutdown()
|
C.vips_shutdown()
|
||||||
initialized = false
|
initialized = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output to stdout vips collected data. Useful for debugging
|
// Output to stdout vips collected data. Useful for debugging
|
||||||
func VipsDebug() {
|
func VipsDebugInfo() {
|
||||||
C.im__print_all()
|
C.im__print_all()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,28 +126,30 @@ func VipsMemory() VipsMemoryInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func vipsExifOrientation(image *C.struct__VipsImage) int {
|
func vipsExifOrientation(image *C.VipsImage) int {
|
||||||
return int(C.vips_exif_orientation(image))
|
return int(C.vips_exif_orientation(image))
|
||||||
}
|
}
|
||||||
|
|
||||||
func vipsHasAlpha(image *C.struct__VipsImage) bool {
|
func vipsHasAlpha(image *C.VipsImage) bool {
|
||||||
return int(C.has_alpha_channel(image)) > 0
|
return int(C.has_alpha_channel(image)) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func vipsHasProfile(image *C.struct__VipsImage) bool {
|
func vipsHasProfile(image *C.VipsImage) bool {
|
||||||
return int(C.has_profile_embed(image)) > 0
|
return int(C.has_profile_embed(image)) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func vipsWindowSize(name string) float64 {
|
func vipsWindowSize(name string) float64 {
|
||||||
return float64(C.interpolator_window_size(C.CString(name)))
|
cname := C.CString(name)
|
||||||
|
defer C.free(unsafe.Pointer(cname))
|
||||||
|
return float64(C.interpolator_window_size(cname))
|
||||||
}
|
}
|
||||||
|
|
||||||
func vipsSpace(image *C.struct__VipsImage) string {
|
func vipsSpace(image *C.VipsImage) string {
|
||||||
return C.GoString(C.vips_enum_nick_bridge(image))
|
return C.GoString(C.vips_enum_nick_bridge(image))
|
||||||
}
|
}
|
||||||
|
|
||||||
func vipsRotate(image *C.struct__VipsImage, angle Angle) (*C.struct__VipsImage, error) {
|
func vipsRotate(image *C.VipsImage, angle Angle) (*C.VipsImage, error) {
|
||||||
var out *C.struct__VipsImage
|
var out *C.VipsImage
|
||||||
defer C.g_object_unref(C.gpointer(image))
|
defer C.g_object_unref(C.gpointer(image))
|
||||||
|
|
||||||
err := C.vips_rotate(image, &out, C.int(angle))
|
err := C.vips_rotate(image, &out, C.int(angle))
|
||||||
|
|
@ -132,8 +160,8 @@ func vipsRotate(image *C.struct__VipsImage, angle Angle) (*C.struct__VipsImage,
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func vipsFlip(image *C.struct__VipsImage, direction Direction) (*C.struct__VipsImage, error) {
|
func vipsFlip(image *C.VipsImage, direction Direction) (*C.VipsImage, error) {
|
||||||
var out *C.struct__VipsImage
|
var out *C.VipsImage
|
||||||
defer C.g_object_unref(C.gpointer(image))
|
defer C.g_object_unref(C.gpointer(image))
|
||||||
|
|
||||||
err := C.vips_flip_bridge(image, &out, C.int(direction))
|
err := C.vips_flip_bridge(image, &out, C.int(direction))
|
||||||
|
|
@ -144,8 +172,8 @@ func vipsFlip(image *C.struct__VipsImage, direction Direction) (*C.struct__VipsI
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func vipsZoom(image *C.struct__VipsImage, zoom int) (*C.struct__VipsImage, error) {
|
func vipsZoom(image *C.VipsImage, zoom int) (*C.VipsImage, error) {
|
||||||
var out *C.struct__VipsImage
|
var out *C.VipsImage
|
||||||
defer C.g_object_unref(C.gpointer(image))
|
defer C.g_object_unref(C.gpointer(image))
|
||||||
|
|
||||||
err := C.vips_zoom_bridge(image, &out, C.int(zoom), C.int(zoom))
|
err := C.vips_zoom_bridge(image, &out, C.int(zoom), C.int(zoom))
|
||||||
|
|
@ -156,36 +184,8 @@ func vipsZoom(image *C.struct__VipsImage, zoom int) (*C.struct__VipsImage, error
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func vipsColorSpace(image *C.struct__VipsImage) (*C.struct__VipsImage, error) {
|
func vipsWatermark(image *C.VipsImage, w Watermark) (*C.VipsImage, error) {
|
||||||
var out *C.struct__VipsImage
|
var out *C.VipsImage
|
||||||
var temp *C.struct__VipsImage
|
|
||||||
var max *C.double
|
|
||||||
var x *C.int
|
|
||||||
var y *C.int
|
|
||||||
|
|
||||||
defer C.g_object_unref(C.gpointer(image))
|
|
||||||
|
|
||||||
err := C.vips_colorspace_bridge(image, &out)
|
|
||||||
if err != 0 {
|
|
||||||
return nil, catchVipsError()
|
|
||||||
}
|
|
||||||
|
|
||||||
err = C.vips_hist_find_ndim_bridge(out, &temp)
|
|
||||||
if err != 0 {
|
|
||||||
return nil, catchVipsError()
|
|
||||||
}
|
|
||||||
|
|
||||||
err = C.vips_max_bridge(temp, max, &x, &y)
|
|
||||||
if err != 0 {
|
|
||||||
return nil, catchVipsError()
|
|
||||||
}
|
|
||||||
debug("MAX VALUE %dx%d", x, y)
|
|
||||||
|
|
||||||
return temp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func vipsWatermark(image *C.struct__VipsImage, w Watermark) (*C.struct__VipsImage, error) {
|
|
||||||
var out *C.struct__VipsImage
|
|
||||||
|
|
||||||
// Defaults
|
// Defaults
|
||||||
noReplicate := 0
|
noReplicate := 0
|
||||||
|
|
@ -203,7 +203,7 @@ func vipsWatermark(image *C.struct__VipsImage, w Watermark) (*C.struct__VipsImag
|
||||||
defer C.free(unsafe.Pointer(text))
|
defer C.free(unsafe.Pointer(text))
|
||||||
defer C.free(unsafe.Pointer(font))
|
defer C.free(unsafe.Pointer(font))
|
||||||
|
|
||||||
err := C.vips_watermark(image, &out, (*C.watermarkTextOptions)(unsafe.Pointer(&textOpts)), (*C.watermarkOptions)(unsafe.Pointer(&opts)))
|
err := C.vips_watermark(image, &out, (*C.WatermarkTextOptions)(unsafe.Pointer(&textOpts)), (*C.WatermarkOptions)(unsafe.Pointer(&opts)))
|
||||||
if err != 0 {
|
if err != 0 {
|
||||||
return nil, catchVipsError()
|
return nil, catchVipsError()
|
||||||
}
|
}
|
||||||
|
|
@ -211,8 +211,8 @@ func vipsWatermark(image *C.struct__VipsImage, w Watermark) (*C.struct__VipsImag
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func vipsRead(buf []byte) (*C.struct__VipsImage, ImageType, error) {
|
func vipsRead(buf []byte) (*C.VipsImage, ImageType, error) {
|
||||||
var image *C.struct__VipsImage
|
var image *C.VipsImage
|
||||||
imageType := vipsImageType(buf)
|
imageType := vipsImageType(buf)
|
||||||
|
|
||||||
if imageType == UNKNOWN {
|
if imageType == UNKNOWN {
|
||||||
|
|
@ -230,46 +230,130 @@ func vipsRead(buf []byte) (*C.struct__VipsImage, ImageType, error) {
|
||||||
return image, imageType, nil
|
return image, imageType, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func vipsSave(image *C.struct__VipsImage, o vipsSaveOptions) ([]byte, error) {
|
func vipsColourspaceIsSupportedBuffer(buf []byte) (bool, error) {
|
||||||
var ptr unsafe.Pointer
|
image, _, err := vipsRead(buf)
|
||||||
length := C.size_t(0)
|
if err != nil {
|
||||||
err := C.int(0)
|
return false, err
|
||||||
|
}
|
||||||
|
C.g_object_unref(C.gpointer(image))
|
||||||
|
return vipsColourspaceIsSupported(image), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func vipsColourspaceIsSupported(image *C.VipsImage) bool {
|
||||||
|
return int(C.vips_colourspace_issupported_bridge(image)) == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func vipsInterpretationBuffer(buf []byte) (Interpretation, error) {
|
||||||
|
image, _, err := vipsRead(buf)
|
||||||
|
if err != nil {
|
||||||
|
return INTERPRETATION_ERROR, err
|
||||||
|
}
|
||||||
|
C.g_object_unref(C.gpointer(image))
|
||||||
|
return vipsInterpretation(image), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func vipsInterpretation(image *C.VipsImage) Interpretation {
|
||||||
|
return Interpretation(C.vips_image_guess_interpretation_bridge(image))
|
||||||
|
}
|
||||||
|
|
||||||
|
func vipsFlattenBackground(image *C.VipsImage, background Color) (*C.VipsImage, error) {
|
||||||
|
var outImage *C.VipsImage
|
||||||
|
|
||||||
|
backgroundC := [3]C.double{
|
||||||
|
C.double(background.R),
|
||||||
|
C.double(background.G),
|
||||||
|
C.double(background.B),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := C.vips_flatten_background_brigde(image, &outImage, (*C.double)(&backgroundC[0]))
|
||||||
|
if int(err) != 0 {
|
||||||
|
return nil, catchVipsError()
|
||||||
|
}
|
||||||
|
|
||||||
|
C.g_object_unref(C.gpointer(image))
|
||||||
|
image = outImage
|
||||||
|
|
||||||
|
return image, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func vipsPreSave(image *C.VipsImage, o *vipsSaveOptions) (*C.VipsImage, error) {
|
||||||
|
// Remove ICC profile metadata
|
||||||
|
if o.NoProfile {
|
||||||
|
C.remove_profile(image)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use a default interpretation and cast it to C type
|
||||||
|
if o.Interpretation == 0 {
|
||||||
|
o.Interpretation = INTERPRETATION_sRGB
|
||||||
|
}
|
||||||
|
interpretation := C.VipsInterpretation(o.Interpretation)
|
||||||
|
|
||||||
|
// Apply the proper colour space
|
||||||
|
var outImage *C.VipsImage
|
||||||
|
if vipsColourspaceIsSupported(image) {
|
||||||
|
err := C.vips_colourspace_bridge(image, &outImage, interpretation)
|
||||||
|
if int(err) != 0 {
|
||||||
|
return nil, catchVipsError()
|
||||||
|
}
|
||||||
|
image = outImage
|
||||||
|
}
|
||||||
|
|
||||||
|
return image, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func vipsSave(image *C.VipsImage, o vipsSaveOptions) ([]byte, error) {
|
||||||
defer C.g_object_unref(C.gpointer(image))
|
defer C.g_object_unref(C.gpointer(image))
|
||||||
|
|
||||||
switch {
|
tmpImage, err := vipsPreSave(image, &o)
|
||||||
case o.Type == PNG:
|
if err != nil {
|
||||||
err = C.vips_pngsave_bridge(image, &ptr, &length, 1, C.int(o.Compression), C.int(o.Quality), 0)
|
return nil, err
|
||||||
|
}
|
||||||
|
defer C.g_object_unref(C.gpointer(tmpImage))
|
||||||
|
|
||||||
|
length := C.size_t(0)
|
||||||
|
saveErr := C.int(0)
|
||||||
|
interlace := C.int(boolToInt(o.Interlace))
|
||||||
|
quality := C.int(o.Quality)
|
||||||
|
|
||||||
|
var ptr unsafe.Pointer
|
||||||
|
switch o.Type {
|
||||||
|
case WEBP:
|
||||||
|
saveErr = C.vips_webpsave_bridge(tmpImage, &ptr, &length, 1, quality)
|
||||||
break
|
break
|
||||||
case o.Type == WEBP:
|
case PNG:
|
||||||
err = C.vips_webpsave_bridge(image, &ptr, &length, 1, C.int(o.Quality), 0)
|
saveErr = C.vips_pngsave_bridge(tmpImage, &ptr, &length, 1, C.int(o.Compression), quality, interlace)
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
err = C.vips_jpegsave_bridge(image, &ptr, &length, 1, C.int(o.Quality), 0)
|
saveErr = C.vips_jpegsave_bridge(tmpImage, &ptr, &length, 1, quality, interlace)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if int(err) != 0 {
|
if int(saveErr) != 0 {
|
||||||
return nil, catchVipsError()
|
return nil, catchVipsError()
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := C.GoBytes(ptr, C.int(length))
|
buf := C.GoBytes(ptr, C.int(length))
|
||||||
|
|
||||||
// Cleanup
|
// Clean up
|
||||||
C.g_free(C.gpointer(ptr))
|
C.g_free(C.gpointer(ptr))
|
||||||
C.vips_error_clear()
|
C.vips_error_clear()
|
||||||
|
|
||||||
return buf, nil
|
return buf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func vipsExtract(image *C.struct__VipsImage, left, top, width, height int) (*C.struct__VipsImage, error) {
|
func max(x int) int {
|
||||||
var buf *C.struct__VipsImage
|
return int(math.Max(float64(x), 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
func vipsExtract(image *C.VipsImage, left, top, width, height int) (*C.VipsImage, error) {
|
||||||
|
var buf *C.VipsImage
|
||||||
defer C.g_object_unref(C.gpointer(image))
|
defer C.g_object_unref(C.gpointer(image))
|
||||||
|
|
||||||
if width > MAX_SIZE || height > MAX_SIZE {
|
if width > MAX_SIZE || height > MAX_SIZE {
|
||||||
return nil, errors.New("Maximum image size exceeded")
|
return nil, errors.New("Maximum image size exceeded")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
top, left = max(top), max(left)
|
||||||
err := C.vips_extract_area_bridge(image, &buf, C.int(left), C.int(top), C.int(width), C.int(height))
|
err := C.vips_extract_area_bridge(image, &buf, C.int(left), C.int(top), C.int(width), C.int(height))
|
||||||
if err != 0 {
|
if err != 0 {
|
||||||
return nil, catchVipsError()
|
return nil, catchVipsError()
|
||||||
|
|
@ -278,11 +362,12 @@ func vipsExtract(image *C.struct__VipsImage, left, top, width, height int) (*C.s
|
||||||
return buf, nil
|
return buf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func vipsShrinkJpeg(buf []byte, input *C.struct__VipsImage, shrink int) (*C.struct__VipsImage, error) {
|
func vipsShrinkJpeg(buf []byte, input *C.VipsImage, shrink int) (*C.VipsImage, error) {
|
||||||
var image *C.struct__VipsImage
|
var image *C.VipsImage
|
||||||
|
var ptr = unsafe.Pointer(&buf[0])
|
||||||
defer C.g_object_unref(C.gpointer(input))
|
defer C.g_object_unref(C.gpointer(input))
|
||||||
|
|
||||||
err := C.vips_jpegload_buffer_shrink(unsafe.Pointer(&buf[0]), C.size_t(len(buf)), &image, C.int(shrink))
|
err := C.vips_jpegload_buffer_shrink(ptr, C.size_t(len(buf)), &image, C.int(shrink))
|
||||||
if err != 0 {
|
if err != 0 {
|
||||||
return nil, catchVipsError()
|
return nil, catchVipsError()
|
||||||
}
|
}
|
||||||
|
|
@ -290,8 +375,8 @@ func vipsShrinkJpeg(buf []byte, input *C.struct__VipsImage, shrink int) (*C.stru
|
||||||
return image, nil
|
return image, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func vipsShrink(input *C.struct__VipsImage, shrink int) (*C.struct__VipsImage, error) {
|
func vipsShrink(input *C.VipsImage, shrink int) (*C.VipsImage, error) {
|
||||||
var image *C.struct__VipsImage
|
var image *C.VipsImage
|
||||||
defer C.g_object_unref(C.gpointer(input))
|
defer C.g_object_unref(C.gpointer(input))
|
||||||
|
|
||||||
err := C.vips_shrink_bridge(input, &image, C.double(float64(shrink)), C.double(float64(shrink)))
|
err := C.vips_shrink_bridge(input, &image, C.double(float64(shrink)), C.double(float64(shrink)))
|
||||||
|
|
@ -302,8 +387,8 @@ func vipsShrink(input *C.struct__VipsImage, shrink int) (*C.struct__VipsImage, e
|
||||||
return image, nil
|
return image, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func vipsEmbed(input *C.struct__VipsImage, left, top, width, height, extend int) (*C.struct__VipsImage, error) {
|
func vipsEmbed(input *C.VipsImage, left, top, width, height, extend int) (*C.VipsImage, error) {
|
||||||
var image *C.struct__VipsImage
|
var image *C.VipsImage
|
||||||
defer C.g_object_unref(C.gpointer(input))
|
defer C.g_object_unref(C.gpointer(input))
|
||||||
|
|
||||||
err := C.vips_embed_bridge(input, &image, C.int(left), C.int(top), C.int(width), C.int(height), C.int(extend))
|
err := C.vips_embed_bridge(input, &image, C.int(left), C.int(top), C.int(width), C.int(height), C.int(extend))
|
||||||
|
|
@ -314,16 +399,16 @@ func vipsEmbed(input *C.struct__VipsImage, left, top, width, height, extend int)
|
||||||
return image, nil
|
return image, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func vipsAffine(input *C.struct__VipsImage, residual float64, i Interpolator) (*C.struct__VipsImage, error) {
|
func vipsAffine(input *C.VipsImage, residualx, residualy float64, i Interpolator) (*C.VipsImage, error) {
|
||||||
var image *C.struct__VipsImage
|
var image *C.VipsImage
|
||||||
istring := C.CString(i.String())
|
cstring := C.CString(i.String())
|
||||||
interpolator := C.vips_interpolate_new(istring)
|
interpolator := C.vips_interpolate_new(cstring)
|
||||||
|
|
||||||
defer C.free(unsafe.Pointer(istring))
|
defer C.free(unsafe.Pointer(cstring))
|
||||||
defer C.g_object_unref(C.gpointer(input))
|
defer C.g_object_unref(C.gpointer(input))
|
||||||
defer C.g_object_unref(C.gpointer(interpolator))
|
defer C.g_object_unref(C.gpointer(interpolator))
|
||||||
|
|
||||||
err := C.vips_affine_interpolator(input, &image, C.double(residual), 0, 0, C.double(residual), interpolator)
|
err := C.vips_affine_interpolator(input, &image, C.double(residualx), 0, 0, C.double(residualy), interpolator)
|
||||||
if err != 0 {
|
if err != 0 {
|
||||||
return nil, catchVipsError()
|
return nil, catchVipsError()
|
||||||
}
|
}
|
||||||
|
|
@ -331,36 +416,37 @@ func vipsAffine(input *C.struct__VipsImage, residual float64, i Interpolator) (*
|
||||||
return image, nil
|
return image, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func vipsImageType(buf []byte) ImageType {
|
func vipsImageType(bytes []byte) ImageType {
|
||||||
imageType := UNKNOWN
|
if len(bytes) == 0 {
|
||||||
|
return UNKNOWN
|
||||||
if len(buf) == 0 {
|
|
||||||
return imageType
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if bytes[0] == 0x89 && bytes[1] == 0x50 && bytes[2] == 0x4E && bytes[3] == 0x47 {
|
||||||
|
return PNG
|
||||||
|
}
|
||||||
|
if bytes[0] == 0xFF && bytes[1] == 0xD8 && bytes[2] == 0xFF {
|
||||||
|
return JPEG
|
||||||
|
}
|
||||||
|
if bytes[8] == 0x57 && bytes[9] == 0x45 && bytes[10] == 0x42 && bytes[11] == 0x50 {
|
||||||
|
return WEBP
|
||||||
|
}
|
||||||
|
if (bytes[0] == 0x49 && bytes[1] == 0x49 && bytes[2] == 0x2A && bytes[3] == 0x0) ||
|
||||||
|
(bytes[0] == 0x4D && bytes[1] == 0x4D && bytes[2] == 0x0 && bytes[3] == 0x2A) {
|
||||||
|
return TIFF
|
||||||
|
}
|
||||||
|
if HasMagickSupport && strings.HasSuffix(readImageType(bytes), "MagickBuffer") {
|
||||||
|
return MAGICK
|
||||||
|
}
|
||||||
|
|
||||||
|
return UNKNOWN
|
||||||
|
}
|
||||||
|
|
||||||
|
func readImageType(buf []byte) string {
|
||||||
length := C.size_t(len(buf))
|
length := C.size_t(len(buf))
|
||||||
imageBuf := unsafe.Pointer(&buf[0])
|
imageBuf := unsafe.Pointer(&buf[0])
|
||||||
bufferType := C.GoString(C.vips_foreign_find_load_buffer(imageBuf, length))
|
load := C.vips_foreign_find_load_buffer(imageBuf, length)
|
||||||
|
defer C.free(imageBuf)
|
||||||
switch {
|
return C.GoString(load)
|
||||||
case strings.HasSuffix(bufferType, "JpegBuffer"):
|
|
||||||
imageType = JPEG
|
|
||||||
break
|
|
||||||
case strings.HasSuffix(bufferType, "PngBuffer"):
|
|
||||||
imageType = PNG
|
|
||||||
break
|
|
||||||
case strings.HasSuffix(bufferType, "TiffBuffer"):
|
|
||||||
imageType = TIFF
|
|
||||||
break
|
|
||||||
case strings.HasSuffix(bufferType, "WebpBuffer"):
|
|
||||||
imageType = WEBP
|
|
||||||
break
|
|
||||||
case strings.HasSuffix(bufferType, "MagickBuffer"):
|
|
||||||
imageType = MAGICK
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
return imageType
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func catchVipsError() error {
|
func catchVipsError() error {
|
||||||
|
|
@ -369,3 +455,32 @@ func catchVipsError() error {
|
||||||
C.vips_thread_shutdown()
|
C.vips_thread_shutdown()
|
||||||
return errors.New(s)
|
return errors.New(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func boolToInt(b bool) int {
|
||||||
|
if b {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func vipsGaussianBlur(image *C.VipsImage, o GaussianBlur) (*C.VipsImage, error) {
|
||||||
|
var out *C.VipsImage
|
||||||
|
defer C.g_object_unref(C.gpointer(image))
|
||||||
|
|
||||||
|
err := C.vips_gaussblur_bridge(image, &out, C.double(o.Sigma), C.double(o.MinAmpl))
|
||||||
|
if err != 0 {
|
||||||
|
return nil, catchVipsError()
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func vipsSharpen(image *C.VipsImage, o Sharpen) (*C.VipsImage, error) {
|
||||||
|
var out *C.VipsImage
|
||||||
|
defer C.g_object_unref(C.gpointer(image))
|
||||||
|
|
||||||
|
err := C.vips_sharpen_bridge(image, &out, C.int(o.Radius), C.double(o.X1), C.double(o.Y2), C.double(o.Y3), C.double(o.M1), C.double(o.M2))
|
||||||
|
if err != 0 {
|
||||||
|
return nil, catchVipsError()
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
404
vips.h
404
vips.h
|
|
@ -2,6 +2,28 @@
|
||||||
#include <vips/vips.h>
|
#include <vips/vips.h>
|
||||||
#include <vips/vips7compat.h>
|
#include <vips/vips7compat.h>
|
||||||
|
|
||||||
|
#ifdef VIPS_MAGICK_H
|
||||||
|
#define VIPS_MAGICK_SUPPORT 1
|
||||||
|
#else
|
||||||
|
#define VIPS_MAGICK_SUPPORT 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starting libvips 7.41, VIPS_ANGLE_x has been renamed to VIPS_ANGLE_Dx
|
||||||
|
* "to help python". So we provide the macro to correctly build for versions
|
||||||
|
* before 7.41.x.
|
||||||
|
* https://github.com/jcupitt/libvips/blob/master/ChangeLog#L128
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if (VIPS_MAJOR_VERSION == 7 && VIPS_MINOR_VERSION < 41)
|
||||||
|
#define VIPS_ANGLE_D0 VIPS_ANGLE_0
|
||||||
|
#define VIPS_ANGLE_D90 VIPS_ANGLE_90
|
||||||
|
#define VIPS_ANGLE_D180 VIPS_ANGLE_180
|
||||||
|
#define VIPS_ANGLE_D270 VIPS_ANGLE_270
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define EXIF_IFD0_ORIENTATION "exif-ifd0-Orientation"
|
||||||
|
|
||||||
enum types {
|
enum types {
|
||||||
UNKNOWN = 0,
|
UNKNOWN = 0,
|
||||||
JPEG,
|
JPEG,
|
||||||
|
|
@ -12,46 +34,91 @@ enum types {
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char *Text;
|
const char *Text;
|
||||||
char *Font;
|
const char *Font;
|
||||||
} watermarkTextOptions;
|
} WatermarkTextOptions;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int Width;
|
int Width;
|
||||||
int DPI;
|
int DPI;
|
||||||
int Margin;
|
int Margin;
|
||||||
int NoReplicate;
|
int NoReplicate;
|
||||||
float Opacity;
|
float Opacity;
|
||||||
double Background[3];
|
double Background[3];
|
||||||
} watermarkOptions;
|
} WatermarkOptions;
|
||||||
|
|
||||||
|
static int
|
||||||
|
has_profile_embed(VipsImage *image) {
|
||||||
|
return vips_image_get_typeof(image, VIPS_META_ICC_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
remove_profile(VipsImage *image) {
|
||||||
|
vips_image_remove(image, VIPS_META_ICC_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
with_interlace(int interlace) {
|
||||||
|
return interlace > 0 ? TRUE : FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
has_alpha_channel(VipsImage *image) {
|
||||||
|
return (
|
||||||
|
(image->Bands == 2 && image->Type == VIPS_INTERPRETATION_B_W) ||
|
||||||
|
(image->Bands == 4 && image->Type != VIPS_INTERPRETATION_CMYK) ||
|
||||||
|
(image->Bands == 5 && image->Type == VIPS_INTERPRETATION_CMYK)
|
||||||
|
) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is here to handle the weird initialization of the vips lib.
|
||||||
|
* libvips use a macro VIPS_INIT() that call vips__init() in version < 7.41,
|
||||||
|
* or calls vips_init() in version >= 7.41.
|
||||||
|
*
|
||||||
|
* Anyway, it's not possible to build bimg on Debian Jessie with libvips 7.40.x,
|
||||||
|
* as vips_init() is a macro to VIPS_INIT(), which is also a macro, hence, cgo
|
||||||
|
* is unable to determine the return type of vips_init(), making the build impossible.
|
||||||
|
* In order to correctly build bimg, for version < 7.41, we should undef vips_init and
|
||||||
|
* creates a vips_init() method that calls VIPS_INIT().
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if (VIPS_MAJOR_VERSION == 7 && VIPS_MINOR_VERSION < 41)
|
||||||
|
#undef vips_init
|
||||||
|
int
|
||||||
|
vips_init(const char *argv0)
|
||||||
|
{
|
||||||
|
return VIPS_INIT(argv0);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void
|
||||||
|
vips_enable_cache_set_trace() {
|
||||||
|
vips_cache_set_trace(TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
vips_affine_interpolator(VipsImage *in, VipsImage **out, double a, double b, double c, double d, VipsInterpolate *interpolator)
|
vips_affine_interpolator(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);
|
return vips_affine(in, out, a, b, c, d, "interpolate", interpolator, NULL);
|
||||||
};
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
vips_jpegload_buffer_shrink(void *buf, size_t len, VipsImage **out, int shrink)
|
vips_jpegload_buffer_shrink(void *buf, size_t len, VipsImage **out, int shrink) {
|
||||||
{
|
|
||||||
return vips_jpegload_buffer(buf, len, out, "shrink", shrink, NULL);
|
return vips_jpegload_buffer(buf, len, out, "shrink", shrink, NULL);
|
||||||
};
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
vips_flip_bridge(VipsImage *in, VipsImage **out, int direction)
|
vips_flip_bridge(VipsImage *in, VipsImage **out, int direction) {
|
||||||
{
|
|
||||||
return vips_flip(in, out, direction, NULL);
|
return vips_flip(in, out, direction, NULL);
|
||||||
};
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
vips_shrink_bridge(VipsImage *in, VipsImage **out, double xshrink, double yshrink)
|
vips_shrink_bridge(VipsImage *in, VipsImage **out, double xshrink, double yshrink) {
|
||||||
{
|
|
||||||
return vips_shrink(in, out, xshrink, yshrink, NULL);
|
return vips_shrink(in, out, xshrink, yshrink, NULL);
|
||||||
};
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
vips_rotate(VipsImage *in, VipsImage **buf, int angle)
|
vips_rotate(VipsImage *in, VipsImage **out, int angle) {
|
||||||
{
|
|
||||||
int rotate = VIPS_ANGLE_D0;
|
int rotate = VIPS_ANGLE_D0;
|
||||||
|
|
||||||
if (angle == 90) {
|
if (angle == 90) {
|
||||||
|
|
@ -62,35 +129,21 @@ vips_rotate(VipsImage *in, VipsImage **buf, int angle)
|
||||||
rotate = VIPS_ANGLE_D270;
|
rotate = VIPS_ANGLE_D270;
|
||||||
}
|
}
|
||||||
|
|
||||||
return vips_rot(in, buf, rotate, NULL);
|
return vips_rot(in, out, rotate, NULL);
|
||||||
};
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
vips_exif_orientation(VipsImage *image) {
|
vips_exif_orientation(VipsImage *image) {
|
||||||
int orientation = 0;
|
int orientation = 0;
|
||||||
const char **exif;
|
const char *exif;
|
||||||
if (
|
if (
|
||||||
vips_image_get_typeof(image, "exif-ifd0-Orientation") != 0 &&
|
vips_image_get_typeof(image, EXIF_IFD0_ORIENTATION) != 0 &&
|
||||||
!vips_image_get_string(image, "exif-ifd0-Orientation", exif)
|
!vips_image_get_string(image, EXIF_IFD0_ORIENTATION, &exif)
|
||||||
) {
|
) {
|
||||||
orientation = atoi(exif[0]);
|
orientation = atoi(&exif[0]);
|
||||||
}
|
}
|
||||||
return orientation;
|
return orientation;
|
||||||
};
|
}
|
||||||
|
|
||||||
int
|
|
||||||
has_profile_embed(VipsImage *image) {
|
|
||||||
return (vips_image_get_typeof(image, VIPS_META_ICC_NAME) > 0) ? 1 : 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
int
|
|
||||||
has_alpha_channel(VipsImage *image) {
|
|
||||||
return (
|
|
||||||
(image->Bands == 2 && image->Type == VIPS_INTERPRETATION_B_W) ||
|
|
||||||
(image->Bands == 4 && image->Type != VIPS_INTERPRETATION_CMYK) ||
|
|
||||||
(image->Bands == 5 && image->Type == VIPS_INTERPRETATION_CMYK)
|
|
||||||
) ? 1 : 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
int
|
int
|
||||||
interpolator_window_size(char const *name) {
|
interpolator_window_size(char const *name) {
|
||||||
|
|
@ -98,155 +151,202 @@ interpolator_window_size(char const *name) {
|
||||||
int window_size = vips_interpolate_get_window_size(interpolator);
|
int window_size = vips_interpolate_get_window_size(interpolator);
|
||||||
g_object_unref(interpolator);
|
g_object_unref(interpolator);
|
||||||
return window_size;
|
return window_size;
|
||||||
};
|
}
|
||||||
|
|
||||||
const char *
|
const char *
|
||||||
vips_enum_nick_bridge(VipsImage *image) {
|
vips_enum_nick_bridge(VipsImage *image) {
|
||||||
return vips_enum_nick(VIPS_TYPE_INTERPRETATION, image->Type);
|
return vips_enum_nick(VIPS_TYPE_INTERPRETATION, image->Type);
|
||||||
};
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
vips_zoom_bridge(VipsImage *in, VipsImage **out, int xfac, int yfac)
|
vips_zoom_bridge(VipsImage *in, VipsImage **out, int xfac, int yfac) {
|
||||||
{
|
|
||||||
return vips_zoom(in, out, xfac, yfac, NULL);
|
return vips_zoom(in, out, xfac, yfac, NULL);
|
||||||
};
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
vips_colorspace_bridge(VipsImage *in, VipsImage **out)
|
vips_embed_bridge(VipsImage *in, VipsImage **out, int left, int top, int width, int height, int extend) {
|
||||||
{
|
|
||||||
return vips_colourspace(in, out, VIPS_INTERPRETATION_LAB, NULL);
|
|
||||||
};
|
|
||||||
|
|
||||||
int
|
|
||||||
vips_hist_find_ndim_bridge(VipsImage *in, VipsImage **out)
|
|
||||||
{
|
|
||||||
return vips_hist_find_ndim(in, out, "bins", 5, NULL);
|
|
||||||
};
|
|
||||||
|
|
||||||
int
|
|
||||||
vips_max_bridge(VipsImage *in, double *out, int **x, int **y)
|
|
||||||
{
|
|
||||||
double ones[3] = { 1, 1, 1 };
|
|
||||||
return vips_max(in, ones, "x", x, "y", y, NULL);
|
|
||||||
};
|
|
||||||
|
|
||||||
int
|
|
||||||
vips_embed_bridge(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);
|
return vips_embed(in, out, left, top, width, height, "extend", extend, NULL);
|
||||||
};
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
vips_extract_area_bridge(VipsImage *in, VipsImage **out, int left, int top, int width, int height)
|
vips_extract_area_bridge(VipsImage *in, VipsImage **out, int left, int top, int width, int height) {
|
||||||
{
|
|
||||||
return vips_extract_area(in, out, left, top, width, height, NULL);
|
return vips_extract_area(in, out, left, top, width, height, NULL);
|
||||||
};
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
vips_jpegsave_bridge(VipsImage *in, void **buf, size_t *len, int strip, int quality, int interlace)
|
vips_colourspace_issupported_bridge(VipsImage *in) {
|
||||||
{
|
return vips_colourspace_issupported(in) ? 1 : 0;
|
||||||
return vips_jpegsave_buffer(in, buf, len, "strip", strip, "Q", quality, "optimize_coding", TRUE, "interlace", interlace, NULL);
|
}
|
||||||
};
|
|
||||||
|
VipsInterpretation
|
||||||
|
vips_image_guess_interpretation_bridge(VipsImage *in) {
|
||||||
|
return vips_image_guess_interpretation(in);
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
vips_pngsave_bridge(VipsImage *in, void **buf, size_t *len, int strip, int compression, int quality, int interlace)
|
vips_colourspace_bridge(VipsImage *in, VipsImage **out, VipsInterpretation space) {
|
||||||
{
|
return vips_colourspace(in, out, space, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
vips_jpegsave_bridge(VipsImage *in, void **buf, size_t *len, int strip, int quality, int interlace) {
|
||||||
|
return vips_jpegsave_buffer(in, buf, len,
|
||||||
|
"strip", strip,
|
||||||
|
"Q", quality,
|
||||||
|
"optimize_coding", TRUE,
|
||||||
|
"interlace", with_interlace(interlace),
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
vips_pngsave_bridge(VipsImage *in, void **buf, size_t *len, int strip, int compression, int quality, int interlace) {
|
||||||
#if (VIPS_MAJOR_VERSION >= 8 || (VIPS_MAJOR_VERSION >= 7 && VIPS_MINOR_VERSION >= 42))
|
#if (VIPS_MAJOR_VERSION >= 8 || (VIPS_MAJOR_VERSION >= 7 && VIPS_MINOR_VERSION >= 42))
|
||||||
return vips_pngsave_buffer(in, buf, len, "strip", FALSE, "compression", compression,
|
return vips_pngsave_buffer(in, buf, len,
|
||||||
"interlace", interlace, "filter", VIPS_FOREIGN_PNG_FILTER_NONE, NULL);
|
"strip", FALSE,
|
||||||
|
"compression", compression,
|
||||||
|
"interlace", with_interlace(interlace),
|
||||||
|
"filter", VIPS_FOREIGN_PNG_FILTER_NONE,
|
||||||
|
NULL
|
||||||
|
);
|
||||||
#else
|
#else
|
||||||
return vips_pngsave_buffer(in, buf, len, "strip", FALSE, "compression", compression,
|
return vips_pngsave_buffer(in, buf, len,
|
||||||
"interlace", interlace, NULL);
|
"strip", FALSE,
|
||||||
|
"compression", compression,
|
||||||
|
"interlace", with_interlace(interlace),
|
||||||
|
NULL
|
||||||
|
);
|
||||||
#endif
|
#endif
|
||||||
};
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
vips_webpsave_bridge(VipsImage *in, void **buf, size_t *len, int strip, int quality, int interlace)
|
vips_webpsave_bridge(VipsImage *in, void **buf, size_t *len, int strip, int quality) {
|
||||||
{
|
return vips_webpsave_buffer(in, buf, len,
|
||||||
return vips_webpsave_buffer(in, buf, len, "strip", strip, "Q", quality, "optimize_coding", TRUE, "interlace", interlace, NULL);
|
"strip", strip,
|
||||||
};
|
"Q", quality,
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
vips_init_image(void *buf, size_t len, int imageType, VipsImage **out) {
|
vips_flatten_background_brigde(VipsImage *in, VipsImage **out, double background[3]) {
|
||||||
|
VipsArrayDouble *vipsBackground = vips_array_double_new(background, 3);
|
||||||
|
return vips_flatten(in, out,
|
||||||
|
"background", vipsBackground,
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
vips_init_image (void *buf, size_t len, int imageType, VipsImage **out) {
|
||||||
int code = 1;
|
int code = 1;
|
||||||
|
|
||||||
if (imageType == JPEG) {
|
if (imageType == JPEG) {
|
||||||
code = vips_jpegload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL);
|
code = vips_jpegload_buffer(buf, len, out, "access", VIPS_ACCESS_RANDOM, NULL);
|
||||||
} else if (imageType == PNG) {
|
} else if (imageType == PNG) {
|
||||||
code = vips_pngload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL);
|
code = vips_pngload_buffer(buf, len, out, "access", VIPS_ACCESS_RANDOM, NULL);
|
||||||
} else if (imageType == WEBP) {
|
} else if (imageType == WEBP) {
|
||||||
code = vips_webpload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL);
|
code = vips_webpload_buffer(buf, len, out, "access", VIPS_ACCESS_RANDOM, NULL);
|
||||||
} else if (imageType == TIFF) {
|
} else if (imageType == TIFF) {
|
||||||
code = vips_tiffload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL);
|
code = vips_tiffload_buffer(buf, len, out, "access", VIPS_ACCESS_RANDOM, NULL);
|
||||||
#if (VIPS_MAJOR_VERSION >= 8)
|
#if (VIPS_MAJOR_VERSION >= 8)
|
||||||
} else if (imageType == MAGICK) {
|
} else if (imageType == MAGICK) {
|
||||||
code = vips_magickload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL);
|
code = vips_magickload_buffer(buf, len, out, "access", VIPS_ACCESS_RANDOM, NULL);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
return code;
|
return code;
|
||||||
};
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
vips_watermark(VipsImage *in, VipsImage **out, watermarkTextOptions *to, watermarkOptions *o)
|
vips_watermark_replicate (VipsImage *orig, VipsImage *in, VipsImage **out) {
|
||||||
{
|
VipsImage *cache = vips_image_new();
|
||||||
double ones[3] = { 1, 1, 1 };
|
|
||||||
|
|
||||||
VipsImage *base = vips_image_new();
|
if (
|
||||||
VipsImage **t = (VipsImage **) vips_object_local_array(VIPS_OBJECT(base), 12);
|
vips_replicate(in, &cache,
|
||||||
t[0] = in;
|
1 + orig->Xsize / in->Xsize,
|
||||||
|
1 + orig->Ysize / in->Ysize, NULL) ||
|
||||||
|
vips_crop(cache, out, 0, 0, orig->Xsize, orig->Ysize, NULL)
|
||||||
|
) {
|
||||||
|
g_object_unref(cache);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
// Make the mask.
|
g_object_unref(cache);
|
||||||
if (
|
return 0;
|
||||||
vips_text(&t[1], to->Text,
|
}
|
||||||
"width", o->Width,
|
|
||||||
"dpi", o->DPI,
|
|
||||||
"font", to->Font,
|
|
||||||
NULL) ||
|
|
||||||
vips_linear1(t[1], &t[2], o->Opacity, 0.0, NULL) ||
|
|
||||||
vips_cast(t[2], &t[3], VIPS_FORMAT_UCHAR, NULL) ||
|
|
||||||
vips_embed(t[3], &t[4], 100, 100,
|
|
||||||
t[3]->Xsize + o->Margin, t[3]->Ysize + o->Margin, NULL)
|
|
||||||
) {
|
|
||||||
g_object_unref(base);
|
|
||||||
return (1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replicate if necessary
|
int
|
||||||
if (o->NoReplicate != 1 && (
|
vips_watermark(VipsImage *in, VipsImage **out, WatermarkTextOptions *to, WatermarkOptions *o) {
|
||||||
vips_replicate(t[4], &t[5],
|
double ones[3] = { 1, 1, 1 };
|
||||||
1 + t[0]->Xsize / t[4]->Xsize,
|
|
||||||
1 + t[0]->Ysize / t[4]->Ysize, NULL) ||
|
|
||||||
vips_crop(t[5], &t[6], 0, 0,
|
|
||||||
t[0]->Xsize, t[0]->Ysize, NULL)
|
|
||||||
)) {
|
|
||||||
g_object_unref(base);
|
|
||||||
return (1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make the constant image to paint the text with.
|
VipsImage *base = vips_image_new();
|
||||||
if (
|
VipsImage **t = (VipsImage **) vips_object_local_array(VIPS_OBJECT(base), 10);
|
||||||
vips_black(&t[7], 1, 1, NULL) ||
|
t[0] = in;
|
||||||
vips_linear( t[7], &t[8], ones, o->Background, 3, NULL) ||
|
|
||||||
vips_cast(t[8], &t[9], VIPS_FORMAT_UCHAR, NULL) ||
|
|
||||||
vips_copy(t[9], &t[10],
|
|
||||||
"interpretation", t[0]->Type,
|
|
||||||
NULL) ||
|
|
||||||
vips_embed(t[10], &t[11], 0, 0,
|
|
||||||
t[0]->Xsize, t[0]->Ysize,
|
|
||||||
"extend", VIPS_EXTEND_COPY,
|
|
||||||
NULL)
|
|
||||||
) {
|
|
||||||
g_object_unref(base);
|
|
||||||
return (1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Blend the mask and text and write to output.
|
// Make the mask.
|
||||||
if (vips_ifthenelse(t[6], t[11], t[0], out, "blend", TRUE, NULL)) {
|
if (
|
||||||
g_object_unref(base);
|
vips_text(&t[1], to->Text,
|
||||||
return (1);
|
"width", o->Width,
|
||||||
}
|
"dpi", o->DPI,
|
||||||
|
"font", to->Font,
|
||||||
|
NULL) ||
|
||||||
|
vips_linear1(t[1], &t[2], o->Opacity, 0.0, NULL) ||
|
||||||
|
vips_cast(t[2], &t[3], VIPS_FORMAT_UCHAR, NULL) ||
|
||||||
|
vips_embed(t[3], &t[4], 100, 100, t[3]->Xsize + o->Margin, t[3]->Ysize + o->Margin, NULL)
|
||||||
|
) {
|
||||||
|
g_object_unref(base);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
g_object_unref(base);
|
// Replicate if necessary
|
||||||
return (0);
|
if (o->NoReplicate != 1) {
|
||||||
};
|
VipsImage *cache = vips_image_new();
|
||||||
|
if (vips_watermark_replicate(t[0], t[4], &cache)) {
|
||||||
|
g_object_unref(cache);
|
||||||
|
g_object_unref(base);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
g_object_unref(t[4]);
|
||||||
|
t[4] = cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make the constant image to paint the text with.
|
||||||
|
if (
|
||||||
|
vips_black(&t[5], 1, 1, NULL) ||
|
||||||
|
vips_linear(t[5], &t[6], ones, o->Background, 3, NULL) ||
|
||||||
|
vips_cast(t[6], &t[7], VIPS_FORMAT_UCHAR, NULL) ||
|
||||||
|
vips_copy(t[7], &t[8], "interpretation", t[0]->Type, NULL) ||
|
||||||
|
vips_embed(t[8], &t[9], 0, 0, t[0]->Xsize, t[0]->Ysize, "extend", VIPS_EXTEND_COPY, NULL)
|
||||||
|
) {
|
||||||
|
g_object_unref(base);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blend the mask and text and write to output.
|
||||||
|
if (vips_ifthenelse(t[4], t[9], t[0], out, "blend", TRUE, NULL)) {
|
||||||
|
g_object_unref(base);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_object_unref(base);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
vips_gaussblur_bridge(VipsImage *in, VipsImage **out, double sigma, double min_ampl) {
|
||||||
|
#if (VIPS_MAJOR_VERSION == 7 && VIPS_MINOR_VERSION < 41)
|
||||||
|
return vips_gaussblur(in, out, (int) sigma, NULL);
|
||||||
|
#else
|
||||||
|
return vips_gaussblur(in, out, sigma, NULL, "min_ampl", min_ampl, NULL);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
vips_sharpen_bridge(VipsImage *in, VipsImage **out, int radius, double x1, double y2, double y3, double m1, double m2) {
|
||||||
|
#if (VIPS_MAJOR_VERSION == 7 && VIPS_MINOR_VERSION < 41)
|
||||||
|
return vips_sharpen(in, out, radius, x1, y2, y3, m1, m2, NULL);
|
||||||
|
#else
|
||||||
|
return vips_sharpen(in, out, "radius", radius, "x1", x1, "y2", y2, "y3", y3, "m1", m1, "m2", m2, NULL);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
|
||||||
97
vips_test.go
97
vips_test.go
|
|
@ -18,31 +18,19 @@ func TestVipsRead(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
img, _ := os.Open(path.Join("fixtures", file.name))
|
image, imageType, _ := vipsRead(readImage(file.name))
|
||||||
buf, _ := ioutil.ReadAll(img)
|
|
||||||
defer img.Close()
|
|
||||||
|
|
||||||
image, imageType, _ := vipsRead(buf)
|
|
||||||
if image == nil {
|
if image == nil {
|
||||||
t.Fatal("Empty image")
|
t.Fatal("Empty image")
|
||||||
}
|
}
|
||||||
if imageType != file.expected {
|
if imageType != file.expected {
|
||||||
t.Fatal("Empty image")
|
t.Fatal("Invalid image type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVipsSave(t *testing.T) {
|
func TestVipsSave(t *testing.T) {
|
||||||
img, _ := os.Open(path.Join("fixtures", "test.jpg"))
|
image, _, _ := vipsRead(readImage("test.jpg"))
|
||||||
buf, _ := ioutil.ReadAll(img)
|
options := vipsSaveOptions{Quality: 95, Type: JPEG, Interlace: true}
|
||||||
defer img.Close()
|
|
||||||
|
|
||||||
image, _, _ := vipsRead(buf)
|
|
||||||
if image == nil {
|
|
||||||
t.Fatal("Empty image")
|
|
||||||
}
|
|
||||||
|
|
||||||
options := vipsSaveOptions{Quality: 95, Type: JPEG}
|
|
||||||
|
|
||||||
buf, err := vipsSave(image, options)
|
buf, err := vipsSave(image, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -52,3 +40,80 @@ func TestVipsSave(t *testing.T) {
|
||||||
t.Fatal("Empty image")
|
t.Fatal("Empty image")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestVipsRotate(t *testing.T) {
|
||||||
|
image, _, _ := vipsRead(readImage("test.jpg"))
|
||||||
|
|
||||||
|
newImg, err := vipsRotate(image, D90)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Cannot save the image")
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, _ := vipsSave(newImg, vipsSaveOptions{Quality: 95})
|
||||||
|
if len(buf) == 0 {
|
||||||
|
t.Fatal("Empty image")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVipsZoom(t *testing.T) {
|
||||||
|
image, _, _ := vipsRead(readImage("test.jpg"))
|
||||||
|
|
||||||
|
newImg, err := vipsZoom(image, 1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Cannot save the image")
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, _ := vipsSave(newImg, vipsSaveOptions{Quality: 95})
|
||||||
|
if len(buf) == 0 {
|
||||||
|
t.Fatal("Empty image")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVipsWatermark(t *testing.T) {
|
||||||
|
image, _, _ := vipsRead(readImage("test.jpg"))
|
||||||
|
|
||||||
|
watermark := Watermark{
|
||||||
|
Text: "Copy me if you can",
|
||||||
|
Font: "sans bold 12",
|
||||||
|
Opacity: 0.5,
|
||||||
|
Width: 200,
|
||||||
|
DPI: 100,
|
||||||
|
Margin: 100,
|
||||||
|
Background: Color{255, 255, 255},
|
||||||
|
}
|
||||||
|
|
||||||
|
newImg, err := vipsWatermark(image, watermark)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Cannot add watermark: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, _ := vipsSave(newImg, vipsSaveOptions{Quality: 95})
|
||||||
|
if len(buf) == 0 {
|
||||||
|
t.Fatal("Empty image")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVipsImageType(t *testing.T) {
|
||||||
|
imgType := vipsImageType(readImage("test.jpg"))
|
||||||
|
if imgType != JPEG {
|
||||||
|
t.Fatal("Invalid image type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVipsMemory(t *testing.T) {
|
||||||
|
mem := VipsMemory()
|
||||||
|
|
||||||
|
if mem.Memory < 1024 {
|
||||||
|
t.Fatal("Invalid memory")
|
||||||
|
}
|
||||||
|
if mem.Allocations == 0 {
|
||||||
|
t.Fatal("Invalid memory allocations")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func readImage(file string) []byte {
|
||||||
|
img, _ := os.Open(path.Join("fixtures", file))
|
||||||
|
buf, _ := ioutil.ReadAll(img)
|
||||||
|
defer img.Close()
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue