mirror of
https://github.com/talgo-cloud/bimg.git
synced 2026-03-11 00:10:27 -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
|
||||
- tip
|
||||
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 -
|
||||
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.
|
||||
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.
|
||||
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 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),
|
||||
its homologous package built for node.js by [Lovell Fuller](https://github.com/lovell).
|
||||
bimg was heavily inspired in [sharp](https://github.com/lovell/sharp), its homologous package built for [node.js](http://nodejs.org).
|
||||
|
||||
**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
|
||||
|
||||
|
|
@ -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 -
|
||||
```
|
||||
|
||||
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`
|
||||
|
||||
## 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 (fully customizable text-based)
|
||||
- Format conversion (with additional quality/compression settings)
|
||||
- EXIF metadata (size, alpha channel, profile, orientation...)
|
||||
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
|
||||
|
||||
## 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)
|
||||
- [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
|
||||
BenchmarkResizeLargeJpeg 30 46652408 ns/op
|
||||
BenchmarkResizePng 20 57387902 ns/op
|
||||
BenchmarkResizeWebP 500 2453220 ns/op
|
||||
BenchmarkConvertToJpeg 30 35556414 ns/op
|
||||
BenchmarkCrop 30 51768475 ns/op
|
||||
BenchmarkExtract 30 50866406 ns/op
|
||||
ok 9.424s
|
||||
BenchmarkRotateJpeg-8 20 64686945 ns/op
|
||||
BenchmarkResizeLargeJpeg-8 20 63390416 ns/op
|
||||
BenchmarkResizePng-8 100 18147294 ns/op
|
||||
BenchmarkResizeWebP-8 100 20836741 ns/op
|
||||
BenchmarkConvertToJpeg-8 100 12831812 ns/op
|
||||
BenchmarkConvertToPng-8 10 128901422 ns/op
|
||||
BenchmarkConvertToWebp-8 10 204027990 ns/op
|
||||
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
|
||||
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
|
||||
|
||||
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,
|
||||
Quality: 95,
|
||||
Rotate: 180,
|
||||
Interlace: true,
|
||||
}
|
||||
|
||||
buffer, err := bimg.Read("image.jpg")
|
||||
|
|
@ -174,19 +241,17 @@ if err != nil {
|
|||
fmt.Fprintln(os.Stderr, err)
|
||||
}
|
||||
|
||||
options := bimg.Watermark{
|
||||
Watermark{
|
||||
Text: "Chuck Norris - Copyright (c) 2315",
|
||||
Opacity: 0.25,
|
||||
Width: 200,
|
||||
DPI: 100,
|
||||
Margin: 150,
|
||||
Font: "sans bold 12",
|
||||
Background: bimg.Color{255, 255, 255},
|
||||
}
|
||||
watermark := bimg.Watermark{
|
||||
Text: "Chuck Norris (c) 2315",
|
||||
Opacity: 0.25,
|
||||
Width: 200,
|
||||
DPI: 100,
|
||||
Margin: 150,
|
||||
Font: "sans bold 12",
|
||||
Background: bimg.Color{255, 255, 255},
|
||||
}
|
||||
|
||||
newImage, err := bimg.NewImage(buffer).Watermark()
|
||||
newImage, err := bimg.NewImage(buffer).Watermark(watermark)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
}
|
||||
|
|
@ -220,349 +285,36 @@ if err != nil {
|
|||
bimg.Write("new.jpg", newImage)
|
||||
```
|
||||
|
||||
#### func DetermineImageTypeName
|
||||
## Debugging
|
||||
|
||||
```go
|
||||
func DetermineImageTypeName(buf []byte) string
|
||||
Run the process passing the `DEBUG` environment variable
|
||||
```
|
||||
Determines the image type format by name (jpeg, png, webp or tiff)
|
||||
|
||||
#### 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)
|
||||
DEBUG=bimg ./app
|
||||
```
|
||||
|
||||
#### func Resize
|
||||
|
||||
```go
|
||||
func Resize(buf []byte, o Options) ([]byte, error)
|
||||
Enable libvips traces (note that a lot of data will be written in stdout):
|
||||
```
|
||||
VIPS_TRACE=1 ./app
|
||||
```
|
||||
|
||||
#### func Shutdown
|
||||
## API
|
||||
|
||||
```go
|
||||
func Shutdown()
|
||||
```
|
||||
Explicit thread-safe libvips shutdown. Call this to drop caches. If libvips was
|
||||
already initialized, the function is no-op
|
||||
See [godoc reference](https://godoc.org/github.com/h2non/bimg) for detailed API documentation.
|
||||
|
||||
#### func VipsDebug
|
||||
## Credits
|
||||
|
||||
```go
|
||||
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
|
||||
People who recurrently contributed to improve `bimg` in some way.
|
||||
|
||||
- [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
|
||||
|
||||
MIT - Tomas Aparicio
|
||||
|
||||
[](https://sourcegraph.com/github.com/h2non/bimg)
|
||||
|
|
|
|||
59
debug.go
59
debug.go
|
|
@ -1,62 +1,5 @@
|
|||
package bimg
|
||||
|
||||
import (
|
||||
"github.com/dustin/go-humanize"
|
||||
. "github.com/tj/go-debug"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
import . "github.com/tj/go-debug"
|
||||
|
||||
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
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
)
|
||||
import "io/ioutil"
|
||||
|
||||
func Read(path string) ([]byte, error) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
buf, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
return ioutil.ReadFile(path)
|
||||
}
|
||||
|
||||
func Write(path string, buf []byte) error {
|
||||
|
|
|
|||
75
image.go
75
image.go
|
|
@ -4,6 +4,11 @@ type Image struct {
|
|||
buffer []byte
|
||||
}
|
||||
|
||||
// Creates a new image
|
||||
func NewImage(buf []byte) *Image {
|
||||
return &Image{buf}
|
||||
}
|
||||
|
||||
// Resize the image to fixed width and height
|
||||
func (i *Image) Resize(width, height int) ([]byte, error) {
|
||||
options := Options{
|
||||
|
|
@ -14,6 +19,27 @@ func (i *Image) Resize(width, height int) ([]byte, error) {
|
|||
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
|
||||
func (i *Image) Extract(top, left, width, height int) ([]byte, error) {
|
||||
options := Options{
|
||||
|
|
@ -22,10 +48,15 @@ func (i *Image) Extract(top, left, width, height int) ([]byte, error) {
|
|||
AreaWidth: width,
|
||||
AreaHeight: height,
|
||||
}
|
||||
|
||||
if top == 0 && left == 0 {
|
||||
options.Top = -1
|
||||
}
|
||||
|
||||
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) {
|
||||
options := Options{
|
||||
Width: width,
|
||||
|
|
@ -35,6 +66,17 @@ func (i *Image) Enlarge(width, height int) ([]byte, error) {
|
|||
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
|
||||
func (i *Image) Crop(width, height int, gravity Gravity) ([]byte, error) {
|
||||
options := Options{
|
||||
|
|
@ -75,15 +117,16 @@ func (i *Image) Thumbnail(pixels int) ([]byte, error) {
|
|||
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) {
|
||||
options := Options{Watermark: w}
|
||||
return i.Process(options)
|
||||
}
|
||||
|
||||
// Zoom the image by the given factor
|
||||
func (i *Image) Zoom(level int) ([]byte, error) {
|
||||
options := Options{Zoom: level}
|
||||
// Zoom the image by the given factor.
|
||||
// You should probably call Extract() before
|
||||
func (i *Image) Zoom(factor int) ([]byte, error) {
|
||||
options := Options{Zoom: factor}
|
||||
return i.Process(options)
|
||||
}
|
||||
|
||||
|
|
@ -111,6 +154,12 @@ func (i *Image) Convert(t ImageType) ([]byte, error) {
|
|||
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
|
||||
func (i *Image) Process(o Options) ([]byte, error) {
|
||||
image, err := Resize(i.buffer, o)
|
||||
|
|
@ -126,6 +175,17 @@ func (i *Image) Metadata() (ImageMetadata, error) {
|
|||
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)
|
||||
func (i *Image) Type() string {
|
||||
return DetermineImageTypeName(i.buffer)
|
||||
|
|
@ -140,8 +200,3 @@ func (i *Image) Size() (ImageSize, error) {
|
|||
func (i *Image) Image() []byte {
|
||||
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)
|
||||
}
|
||||
|
||||
func TestImageExtract(t *testing.T) {
|
||||
buf, err := initImage("test.jpg").Extract(100, 100, 300, 300)
|
||||
func TestImageResizeAndCrop(t *testing.T) {
|
||||
buf, err := initImage("test.jpg").ResizeAndCrop(300, 200)
|
||||
if err != nil {
|
||||
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 {
|
||||
t.Error(err)
|
||||
}
|
||||
|
|
@ -34,6 +48,20 @@ func TestImageExtract(t *testing.T) {
|
|||
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) {
|
||||
buf, err := initImage("test.png").Enlarge(500, 375)
|
||||
if err != nil {
|
||||
|
|
@ -48,10 +76,24 @@ func TestImageEnlarge(t *testing.T) {
|
|||
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) {
|
||||
buf, err := initImage("test.jpg").Crop(800, 600, NORTH)
|
||||
if err != nil {
|
||||
t.Errorf("Cannot process the image: %#v", err)
|
||||
t.Errorf("Cannot process the image: %s", err)
|
||||
}
|
||||
|
||||
err = assertSize(buf, 800, 600)
|
||||
|
|
@ -65,7 +107,7 @@ func TestImageCrop(t *testing.T) {
|
|||
func TestImageCropByWidth(t *testing.T) {
|
||||
buf, err := initImage("test.jpg").CropByWidth(600)
|
||||
if err != nil {
|
||||
t.Errorf("Cannot process the image: %#v", err)
|
||||
t.Errorf("Cannot process the image: %s", err)
|
||||
}
|
||||
|
||||
err = assertSize(buf, 600, 375)
|
||||
|
|
@ -79,7 +121,7 @@ func TestImageCropByWidth(t *testing.T) {
|
|||
func TestImageCropByHeight(t *testing.T) {
|
||||
buf, err := initImage("test.jpg").CropByHeight(300)
|
||||
if err != nil {
|
||||
t.Errorf("Cannot process the image: %#v", err)
|
||||
t.Errorf("Cannot process the image: %s", err)
|
||||
}
|
||||
|
||||
err = assertSize(buf, 480, 300)
|
||||
|
|
@ -93,7 +135,7 @@ func TestImageCropByHeight(t *testing.T) {
|
|||
func TestImageThumbnail(t *testing.T) {
|
||||
buf, err := initImage("test.jpg").Thumbnail(100)
|
||||
if err != nil {
|
||||
t.Errorf("Cannot process the image: %#v", err)
|
||||
t.Errorf("Cannot process the image: %s", err)
|
||||
}
|
||||
|
||||
err = assertSize(buf, 100, 100)
|
||||
|
|
@ -134,13 +176,51 @@ func TestImageWatermark(t *testing.T) {
|
|||
Write("fixtures/test_watermark_out.jpg", buf)
|
||||
}
|
||||
|
||||
func TestImageZoom(t *testing.T) {
|
||||
buf, err := initImage("test.jpg").Zoom(1)
|
||||
func TestImageWatermarkNoReplicate(t *testing.T) {
|
||||
image := initImage("test.jpg")
|
||||
_, err := image.Crop(800, 600, NORTH)
|
||||
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 {
|
||||
t.Error(err)
|
||||
}
|
||||
|
|
@ -180,6 +260,19 @@ func TestImageConvert(t *testing.T) {
|
|||
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) {
|
||||
data, err := initImage("test.png").Metadata()
|
||||
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) {
|
||||
image := initImage("test.jpg")
|
||||
_, err := image.CropByWidth(300)
|
||||
|
|
|
|||
14
metadata.go
14
metadata.go
|
|
@ -18,6 +18,7 @@ type ImageMetadata struct {
|
|||
Profile bool
|
||||
Type string
|
||||
Space string
|
||||
Colourspace string
|
||||
Size ImageSize
|
||||
}
|
||||
|
||||
|
|
@ -34,6 +35,17 @@ func Size(buf []byte) (ImageSize, error) {
|
|||
}, 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...)
|
||||
func Metadata(buf []byte) (ImageMetadata, error) {
|
||||
defer C.vips_thread_shutdown()
|
||||
|
|
@ -56,7 +68,7 @@ func Metadata(buf []byte) (ImageMetadata, error) {
|
|||
Alpha: vipsHasAlpha(image),
|
||||
Profile: vipsHasProfile(image),
|
||||
Space: vipsSpace(image),
|
||||
Type: getImageTypeName(imageType),
|
||||
Type: ImageTypeName(imageType),
|
||||
}
|
||||
|
||||
return metadata, nil
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ func TestSize(t *testing.T) {
|
|||
{"test.png", 400, 300},
|
||||
{"test.webp", 550, 368},
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
size, err := Size(readFile(file.name))
|
||||
if err != nil {
|
||||
|
|
@ -39,15 +38,15 @@ func TestMetadata(t *testing.T) {
|
|||
profile bool
|
||||
space string
|
||||
}{
|
||||
{"test.jpg", "jpeg", 0, false, false, "bicubic"},
|
||||
{"test.png", "png", 0, true, false, "bicubic"},
|
||||
{"test.webp", "webp", 0, false, false, "bicubic"},
|
||||
{"test.jpg", "jpeg", 0, false, false, "srgb"},
|
||||
{"test.png", "png", 0, true, false, "srgb"},
|
||||
{"test.webp", "webp", 0, false, false, "srgb"},
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
metadata, err := Metadata(readFile(file.name))
|
||||
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 {
|
||||
|
|
@ -62,6 +61,58 @@ func TestMetadata(t *testing.T) {
|
|||
if 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
|
||||
|
||||
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
|
||||
D0 Angle = 0
|
||||
D90 Angle = 90
|
||||
D180 Angle = 180
|
||||
D270 Angle = 270
|
||||
)
|
||||
|
||||
type Direction int
|
||||
|
|
@ -55,11 +55,35 @@ const (
|
|||
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
|
||||
type Color struct {
|
||||
R, G, B uint8
|
||||
}
|
||||
|
||||
// Shortcut to black RGB color representation
|
||||
var ColorBlack = Color{0, 0, 0}
|
||||
|
||||
// Text-based watermark configuration
|
||||
type Watermark struct {
|
||||
Width int
|
||||
DPI int
|
||||
|
|
@ -71,27 +95,48 @@ type Watermark struct {
|
|||
Background Color
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
Height int
|
||||
Width int
|
||||
AreaHeight int
|
||||
AreaWidth int
|
||||
Top int
|
||||
Left int
|
||||
Extend int
|
||||
Quality int
|
||||
Compression int
|
||||
Zoom int
|
||||
Crop bool
|
||||
Enlarge bool
|
||||
Embed bool
|
||||
Flip bool
|
||||
Flop bool
|
||||
NoAutoRotate bool
|
||||
Colorspace bool
|
||||
Rotate Angle
|
||||
Gravity Gravity
|
||||
Watermark Watermark
|
||||
Type ImageType
|
||||
Interpolator Interpolator
|
||||
type GaussianBlur struct {
|
||||
Sigma float64
|
||||
MinAmpl float64
|
||||
}
|
||||
|
||||
type Sharpen struct {
|
||||
Radius int
|
||||
X1 float64
|
||||
Y2 float64
|
||||
Y3 float64
|
||||
M1 float64
|
||||
M2 float64
|
||||
}
|
||||
|
||||
// Supported image transformation options
|
||||
type Options struct {
|
||||
Height int
|
||||
Width int
|
||||
AreaHeight int
|
||||
AreaWidth int
|
||||
Top int
|
||||
Left int
|
||||
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
|
||||
}
|
||||
|
||||
// Defaults
|
||||
if o.Quality == 0 {
|
||||
o.Quality = QUALITY
|
||||
}
|
||||
if o.Compression == 0 {
|
||||
o.Compression = 6
|
||||
}
|
||||
if o.Type == 0 {
|
||||
o.Type = imageType
|
||||
}
|
||||
// Clone and define default options
|
||||
o = applyDefaults(o, imageType)
|
||||
|
||||
if IsTypeSupported(o.Type) == false {
|
||||
return nil, errors.New("Unsupported image output type")
|
||||
|
|
@ -43,13 +35,17 @@ func Resize(buf []byte, o Options) ([]byte, error) {
|
|||
inWidth := int(image.Xsize)
|
||||
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
|
||||
factor := imageCalculations(&o, inWidth, inHeight)
|
||||
shrink := int(math.Max(math.Floor(factor), 1))
|
||||
residual := float64(shrink) / factor
|
||||
shrink := calculateShrink(factor, o.Interpolator)
|
||||
residual := calculateResidual(factor, shrink)
|
||||
|
||||
// Do not enlarge the output if the input width *or* height are already less than the required dimensions
|
||||
if o.Enlarge == false {
|
||||
// Do not enlarge the output if the input width or height
|
||||
// are already less than the required dimensions
|
||||
if !o.Enlarge && !o.Force {
|
||||
if inWidth < o.Width && inHeight < o.Height {
|
||||
factor = 1.0
|
||||
shrink = 1
|
||||
|
|
@ -65,81 +61,161 @@ func Resize(buf []byte, o Options) ([]byte, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tmpImage != nil {
|
||||
image = tmpImage
|
||||
factor = math.Max(factor, 1.0)
|
||||
shrink = int(math.Floor(factor))
|
||||
residual = float64(shrink) / factor
|
||||
}
|
||||
|
||||
image = tmpImage
|
||||
factor = math.Max(factor, 1.0)
|
||||
shrink = int(math.Floor(factor))
|
||||
residual = float64(shrink) / factor
|
||||
}
|
||||
|
||||
// Calculate integral box shrink
|
||||
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
|
||||
// Zoom image, if necessary
|
||||
image, err = zoomImage(image, o.Zoom)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Rotate / flip image if necessary
|
||||
image, err = rotateImage(image, o)
|
||||
// Rotate / flip image, if necessary
|
||||
image, err = rotateAndFlipImage(image, o)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
saveOptions := vipsSaveOptions{
|
||||
Quality: o.Quality,
|
||||
Type: o.Type,
|
||||
Compression: o.Compression,
|
||||
// Transform image, if necessary
|
||||
if shouldTransformImage(o, inWidth, inHeight) {
|
||||
image, err = transformImage(image, o, shrink, residual)
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Finally save as buffer
|
||||
buf, err = vipsSave(image, saveOptions)
|
||||
// Flatten image on a background, if necessary
|
||||
image, err = imageFlatten(image, imageType, o)
|
||||
if err != nil {
|
||||
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
|
||||
inWidth := int(image.Xsize)
|
||||
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)))
|
||||
height := int(math.Min(float64(inHeight), float64(o.Height)))
|
||||
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)
|
||||
break
|
||||
case o.Embed:
|
||||
left, top := (o.Width-inWidth)/2, (o.Height-inHeight)/2
|
||||
image, err = vipsEmbed(image, left, top, o.Width, o.Height, o.Extend)
|
||||
break
|
||||
case o.Top > 0 || o.Left > 0:
|
||||
if o.AreaWidth == 0 || o.AreaHeight == 0 {
|
||||
err = errors.New("Area to extract cannot be 0")
|
||||
} else {
|
||||
image, err = vipsExtract(image, o.Left, o.Top, o.AreaWidth, o.AreaHeight)
|
||||
case o.Top != 0 || o.Left != 0:
|
||||
if o.AreaWidth == 0 {
|
||||
o.AreaHeight = o.Width
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
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 direction Direction = -1
|
||||
|
||||
|
|
@ -198,17 +280,17 @@ func rotateImage(image *C.struct__VipsImage, o Options) (*C.struct__VipsImage, e
|
|||
return image, err
|
||||
}
|
||||
|
||||
func watermakImage(image *C.struct__VipsImage, w Watermark) (*C.struct__VipsImage, error) {
|
||||
if len(w.Text) == 0 {
|
||||
func watermakImage(image *C.VipsImage, w Watermark) (*C.VipsImage, error) {
|
||||
if w.Text == "" {
|
||||
return image, nil
|
||||
}
|
||||
|
||||
// Defaults
|
||||
if len(w.Font) == 0 {
|
||||
w.Font = "sans 10"
|
||||
if w.Font == "" {
|
||||
w.Font = WATERMARK_FONT
|
||||
}
|
||||
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 {
|
||||
w.DPI = 150
|
||||
|
|
@ -230,15 +312,23 @@ func watermakImage(image *C.struct__VipsImage, w Watermark) (*C.struct__VipsImag
|
|||
return image, nil
|
||||
}
|
||||
|
||||
func zoomImage(image *C.struct__VipsImage, zoom int) (*C.struct__VipsImage, error) {
|
||||
if zoom == 0 {
|
||||
func imageFlatten(image *C.VipsImage, imageType ImageType, o Options) (*C.VipsImage, error) {
|
||||
// Only PNG images are supported for now
|
||||
if imageType != PNG || o.Background == ColorBlack {
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
image, err := vipsShrink(image, shrink)
|
||||
if err != nil {
|
||||
|
|
@ -258,8 +348,8 @@ func shrinkImage(image *C.struct__VipsImage, o Options, residual float64, shrink
|
|||
return image, residual, nil
|
||||
}
|
||||
|
||||
func shrinkJpegImage(buf []byte, input *C.struct__VipsImage, factor float64, shrink int) (*C.struct__VipsImage, float64, error) {
|
||||
var image *C.struct__VipsImage
|
||||
func shrinkJpegImage(buf []byte, input *C.VipsImage, factor float64, shrink int) (*C.VipsImage, float64, error) {
|
||||
var image *C.VipsImage
|
||||
var err error
|
||||
shrinkOnLoad := 1
|
||||
|
||||
|
|
@ -305,8 +395,8 @@ func imageCalculations(o *Options, inWidth, inHeight int) float64 {
|
|||
case o.Height > 0:
|
||||
factor = yfactor
|
||||
o.Width = int(math.Floor(float64(inWidth) / factor))
|
||||
// Identity transform
|
||||
default:
|
||||
// Identity transform
|
||||
o.Width = inWidth
|
||||
o.Height = inHeight
|
||||
break
|
||||
|
|
@ -337,50 +427,63 @@ func calculateCrop(inWidth, inHeight, outWidth, outHeight int, gravity Gravity)
|
|||
return left, top
|
||||
}
|
||||
|
||||
func calculateRotationAndFlip(image *C.struct__VipsImage, angle Angle) (Angle, bool) {
|
||||
func calculateRotationAndFlip(image *C.VipsImage, angle Angle) (Angle, bool) {
|
||||
rotate := D0
|
||||
flip := false
|
||||
|
||||
if angle == -1 {
|
||||
switch vipsExifOrientation(image) {
|
||||
case 6:
|
||||
rotate = D90
|
||||
break
|
||||
case 3:
|
||||
rotate = D180
|
||||
break
|
||||
case 8:
|
||||
rotate = D270
|
||||
break
|
||||
case 2:
|
||||
flip = true
|
||||
break // flip 1
|
||||
case 7:
|
||||
flip = true
|
||||
rotate = D90
|
||||
break // flip 6
|
||||
case 4:
|
||||
flip = true
|
||||
rotate = D180
|
||||
break // flip 3
|
||||
case 5:
|
||||
flip = true
|
||||
rotate = D270
|
||||
break // flip 8
|
||||
}
|
||||
} else {
|
||||
if angle == 90 {
|
||||
rotate = D90
|
||||
} else if angle == 180 {
|
||||
rotate = D180
|
||||
} else if angle == 270 {
|
||||
rotate = D270
|
||||
}
|
||||
if angle > 0 {
|
||||
return rotate, flip
|
||||
}
|
||||
|
||||
switch vipsExifOrientation(image) {
|
||||
case 6:
|
||||
rotate = D90
|
||||
break
|
||||
case 3:
|
||||
rotate = D180
|
||||
break
|
||||
case 8:
|
||||
rotate = D270
|
||||
break
|
||||
case 2:
|
||||
flip = true
|
||||
break // flip 1
|
||||
case 7:
|
||||
flip = true
|
||||
rotate = D90
|
||||
break // flip 6
|
||||
case 4:
|
||||
flip = true
|
||||
rotate = D180
|
||||
break // flip 3
|
||||
case 5:
|
||||
flip = true
|
||||
rotate = D270
|
||||
break // flip 8
|
||||
}
|
||||
|
||||
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 {
|
||||
divisor := angle % 90
|
||||
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")
|
||||
}
|
||||
|
||||
err = Write("fixtures/test_out.jpg", newImg)
|
||||
if err != nil {
|
||||
t.Fatal("Cannot save the image")
|
||||
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_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")
|
||||
}
|
||||
|
||||
err = Write("fixtures/test_rotate_out.jpg", newImg)
|
||||
if err != nil {
|
||||
t.Fatal("Cannot save the image")
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
size, _ := Size(newImg)
|
||||
if size.Height != options.Width {
|
||||
t.Fatalf("Invalid image size: %dx%d", size.Width, size.Height)
|
||||
}
|
||||
|
||||
if DetermineImageType(newImg) != JPEG {
|
||||
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")
|
||||
}
|
||||
Write("fixtures/test_rotate_out.jpg", newImg)
|
||||
}
|
||||
|
||||
func TestInvalidRotate(t *testing.T) {
|
||||
|
|
@ -96,44 +102,128 @@ func TestInvalidRotate(t *testing.T) {
|
|||
t.Fatal("Image is not jpeg")
|
||||
}
|
||||
|
||||
err = Write("fixtures/test_invalid_rotate_out.jpg", newImg)
|
||||
if err != nil {
|
||||
t.Fatal("Cannot save the image")
|
||||
size, _ := Size(newImg)
|
||||
if size.Height != options.Width {
|
||||
t.Fatalf("Invalid image size: %dx%d", size.Width, size.Height)
|
||||
}
|
||||
|
||||
Write("fixtures/test_invalid_rotate_out.jpg", newImg)
|
||||
}
|
||||
|
||||
func TestConvert(t *testing.T) {
|
||||
width, height := 640, 480
|
||||
|
||||
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)
|
||||
}
|
||||
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) != PNG {
|
||||
t.Fatal("Image is not png")
|
||||
if DetermineImageType(newImg) != JPEG {
|
||||
t.Fatal("Image is not jpeg")
|
||||
}
|
||||
|
||||
size, _ := Size(newImg)
|
||||
if size.Height != height || size.Width != width {
|
||||
t.Fatal("Invalid image size")
|
||||
if size.Height != options.Height || size.Width != options.Width {
|
||||
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 {
|
||||
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")
|
||||
}
|
||||
|
||||
err = Write("fixtures/transparent_out.png", newImg)
|
||||
if err != nil {
|
||||
t.Fatal("Cannot save the image")
|
||||
}
|
||||
Write("fixtures/transparent_out.png", newImg)
|
||||
}
|
||||
|
||||
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) {
|
||||
options := Options{
|
||||
Width: 800,
|
||||
|
|
@ -209,7 +301,17 @@ func BenchmarkConvertToJpeg(b *testing.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{
|
||||
Width: 800,
|
||||
Height: 600,
|
||||
|
|
@ -217,6 +319,22 @@ func BenchmarkCrop(b *testing.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) {
|
||||
options := Options{
|
||||
Top: 100,
|
||||
|
|
@ -226,3 +344,83 @@ func BenchmarkExtractJpeg(b *testing.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
|
||||
)
|
||||
|
||||
// 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)
|
||||
func DetermineImageType(buf []byte) ImageType {
|
||||
return vipsImageType(buf)
|
||||
|
|
@ -18,40 +27,28 @@ func DetermineImageType(buf []byte) ImageType {
|
|||
|
||||
// Determines the image type format by name (jpeg, png, webp or tiff)
|
||||
func DetermineImageTypeName(buf []byte) string {
|
||||
return getImageTypeName(vipsImageType(buf))
|
||||
return ImageTypeName(vipsImageType(buf))
|
||||
}
|
||||
|
||||
// Check if a given image type is supported
|
||||
func IsTypeSupported(t ImageType) bool {
|
||||
return t == JPEG || t == PNG || t == WEBP
|
||||
return ImageTypes[t] != ""
|
||||
}
|
||||
|
||||
// Check if a given image type name is supported
|
||||
func IsTypeNameSupported(t string) bool {
|
||||
return t == "jpeg" || t == "jpg" ||
|
||||
t == "png" || t == "webp"
|
||||
for _, name := range ImageTypes {
|
||||
if name == t {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getImageTypeName(code ImageType) string {
|
||||
imageType := "unknown"
|
||||
|
||||
switch {
|
||||
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
|
||||
func ImageTypeName(t ImageType) string {
|
||||
imageType := ImageTypes[t]
|
||||
if imageType == "" {
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
return imageType
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ func TestIsTypeNameSupported(t *testing.T) {
|
|||
name string
|
||||
expected bool
|
||||
}{
|
||||
{"jpg", true},
|
||||
{"jpeg", true},
|
||||
{"png", true},
|
||||
{"webp", true},
|
||||
{"gif", false},
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
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
|
||||
#include "vips.h"
|
||||
#include "stdlib.h"
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"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 (
|
||||
m sync.Mutex
|
||||
initialized bool = false
|
||||
initialized bool
|
||||
)
|
||||
|
||||
type VipsMemoryInfo struct {
|
||||
|
|
@ -27,9 +38,12 @@ type VipsMemoryInfo struct {
|
|||
}
|
||||
|
||||
type vipsSaveOptions struct {
|
||||
Quality int
|
||||
Compression int
|
||||
Type ImageType
|
||||
Quality int
|
||||
Compression int
|
||||
Type ImageType
|
||||
Interlace bool
|
||||
NoProfile bool
|
||||
Interpretation Interpretation
|
||||
}
|
||||
|
||||
type vipsWatermarkOptions struct {
|
||||
|
|
@ -47,16 +61,16 @@ type vipsWatermarkTextOptions struct {
|
|||
}
|
||||
|
||||
func init() {
|
||||
if C.VIPS_MAJOR_VERSION <= 7 && C.VIPS_MINOR_VERSION < 40 {
|
||||
panic("unsupported old vips version!")
|
||||
}
|
||||
|
||||
Initialize()
|
||||
}
|
||||
|
||||
// Explicit thread-safe start of libvips.
|
||||
// Only call this function if you've previously shutdown libvips
|
||||
func Initialize() {
|
||||
if C.VIPS_MAJOR_VERSION <= 7 && C.VIPS_MINOR_VERSION < 40 {
|
||||
panic("unsupported libvips version!")
|
||||
}
|
||||
|
||||
m.Lock()
|
||||
runtime.LockOSThread()
|
||||
defer m.Unlock()
|
||||
|
|
@ -64,30 +78,42 @@ func Initialize() {
|
|||
|
||||
err := C.vips_init(C.CString("bimg"))
|
||||
if err != 0 {
|
||||
Shutdown()
|
||||
panic("unable to start vips!")
|
||||
}
|
||||
|
||||
C.vips_concurrency_set(0) // default
|
||||
C.vips_cache_set_max_mem(100 * 1024 * 1024) // 100 MB
|
||||
C.vips_cache_set_max(500) // 500 operations
|
||||
// Set libvips cache params
|
||||
C.vips_cache_set_max_mem(maxCacheMem)
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
func Shutdown() {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
if initialized == true {
|
||||
if initialized {
|
||||
C.vips_shutdown()
|
||||
initialized = false
|
||||
}
|
||||
}
|
||||
|
||||
// Output to stdout vips collected data. Useful for debugging
|
||||
func VipsDebug() {
|
||||
func VipsDebugInfo() {
|
||||
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))
|
||||
}
|
||||
|
||||
func vipsHasAlpha(image *C.struct__VipsImage) bool {
|
||||
func vipsHasAlpha(image *C.VipsImage) bool {
|
||||
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
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
func vipsRotate(image *C.struct__VipsImage, angle Angle) (*C.struct__VipsImage, error) {
|
||||
var out *C.struct__VipsImage
|
||||
func vipsRotate(image *C.VipsImage, angle Angle) (*C.VipsImage, error) {
|
||||
var out *C.VipsImage
|
||||
defer C.g_object_unref(C.gpointer(image))
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func vipsFlip(image *C.struct__VipsImage, direction Direction) (*C.struct__VipsImage, error) {
|
||||
var out *C.struct__VipsImage
|
||||
func vipsFlip(image *C.VipsImage, direction Direction) (*C.VipsImage, error) {
|
||||
var out *C.VipsImage
|
||||
defer C.g_object_unref(C.gpointer(image))
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func vipsZoom(image *C.struct__VipsImage, zoom int) (*C.struct__VipsImage, error) {
|
||||
var out *C.struct__VipsImage
|
||||
func vipsZoom(image *C.VipsImage, zoom int) (*C.VipsImage, error) {
|
||||
var out *C.VipsImage
|
||||
defer C.g_object_unref(C.gpointer(image))
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func vipsColorSpace(image *C.struct__VipsImage) (*C.struct__VipsImage, error) {
|
||||
var out *C.struct__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
|
||||
func vipsWatermark(image *C.VipsImage, w Watermark) (*C.VipsImage, error) {
|
||||
var out *C.VipsImage
|
||||
|
||||
// Defaults
|
||||
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(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 {
|
||||
return nil, catchVipsError()
|
||||
}
|
||||
|
|
@ -211,8 +211,8 @@ func vipsWatermark(image *C.struct__VipsImage, w Watermark) (*C.struct__VipsImag
|
|||
return out, nil
|
||||
}
|
||||
|
||||
func vipsRead(buf []byte) (*C.struct__VipsImage, ImageType, error) {
|
||||
var image *C.struct__VipsImage
|
||||
func vipsRead(buf []byte) (*C.VipsImage, ImageType, error) {
|
||||
var image *C.VipsImage
|
||||
imageType := vipsImageType(buf)
|
||||
|
||||
if imageType == UNKNOWN {
|
||||
|
|
@ -230,46 +230,130 @@ func vipsRead(buf []byte) (*C.struct__VipsImage, ImageType, error) {
|
|||
return image, imageType, nil
|
||||
}
|
||||
|
||||
func vipsSave(image *C.struct__VipsImage, o vipsSaveOptions) ([]byte, error) {
|
||||
var ptr unsafe.Pointer
|
||||
length := C.size_t(0)
|
||||
err := C.int(0)
|
||||
func vipsColourspaceIsSupportedBuffer(buf []byte) (bool, error) {
|
||||
image, _, err := vipsRead(buf)
|
||||
if err != nil {
|
||||
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))
|
||||
|
||||
switch {
|
||||
case o.Type == PNG:
|
||||
err = C.vips_pngsave_bridge(image, &ptr, &length, 1, C.int(o.Compression), C.int(o.Quality), 0)
|
||||
tmpImage, err := vipsPreSave(image, &o)
|
||||
if err != nil {
|
||||
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
|
||||
case o.Type == WEBP:
|
||||
err = C.vips_webpsave_bridge(image, &ptr, &length, 1, C.int(o.Quality), 0)
|
||||
case PNG:
|
||||
saveErr = C.vips_pngsave_bridge(tmpImage, &ptr, &length, 1, C.int(o.Compression), quality, interlace)
|
||||
break
|
||||
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
|
||||
}
|
||||
|
||||
if int(err) != 0 {
|
||||
if int(saveErr) != 0 {
|
||||
return nil, catchVipsError()
|
||||
}
|
||||
|
||||
buf := C.GoBytes(ptr, C.int(length))
|
||||
|
||||
// Cleanup
|
||||
// Clean up
|
||||
C.g_free(C.gpointer(ptr))
|
||||
C.vips_error_clear()
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func vipsExtract(image *C.struct__VipsImage, left, top, width, height int) (*C.struct__VipsImage, error) {
|
||||
var buf *C.struct__VipsImage
|
||||
func max(x int) int {
|
||||
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))
|
||||
|
||||
if width > MAX_SIZE || height > MAX_SIZE {
|
||||
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))
|
||||
if err != 0 {
|
||||
return nil, catchVipsError()
|
||||
|
|
@ -278,11 +362,12 @@ func vipsExtract(image *C.struct__VipsImage, left, top, width, height int) (*C.s
|
|||
return buf, nil
|
||||
}
|
||||
|
||||
func vipsShrinkJpeg(buf []byte, input *C.struct__VipsImage, shrink int) (*C.struct__VipsImage, error) {
|
||||
var image *C.struct__VipsImage
|
||||
func vipsShrinkJpeg(buf []byte, input *C.VipsImage, shrink int) (*C.VipsImage, error) {
|
||||
var image *C.VipsImage
|
||||
var ptr = unsafe.Pointer(&buf[0])
|
||||
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 {
|
||||
return nil, catchVipsError()
|
||||
}
|
||||
|
|
@ -290,8 +375,8 @@ func vipsShrinkJpeg(buf []byte, input *C.struct__VipsImage, shrink int) (*C.stru
|
|||
return image, nil
|
||||
}
|
||||
|
||||
func vipsShrink(input *C.struct__VipsImage, shrink int) (*C.struct__VipsImage, error) {
|
||||
var image *C.struct__VipsImage
|
||||
func vipsShrink(input *C.VipsImage, shrink int) (*C.VipsImage, error) {
|
||||
var image *C.VipsImage
|
||||
defer C.g_object_unref(C.gpointer(input))
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func vipsEmbed(input *C.struct__VipsImage, left, top, width, height, extend int) (*C.struct__VipsImage, error) {
|
||||
var image *C.struct__VipsImage
|
||||
func vipsEmbed(input *C.VipsImage, left, top, width, height, extend int) (*C.VipsImage, error) {
|
||||
var image *C.VipsImage
|
||||
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))
|
||||
|
|
@ -314,16 +399,16 @@ func vipsEmbed(input *C.struct__VipsImage, left, top, width, height, extend int)
|
|||
return image, nil
|
||||
}
|
||||
|
||||
func vipsAffine(input *C.struct__VipsImage, residual float64, i Interpolator) (*C.struct__VipsImage, error) {
|
||||
var image *C.struct__VipsImage
|
||||
istring := C.CString(i.String())
|
||||
interpolator := C.vips_interpolate_new(istring)
|
||||
func vipsAffine(input *C.VipsImage, residualx, residualy float64, i Interpolator) (*C.VipsImage, error) {
|
||||
var image *C.VipsImage
|
||||
cstring := C.CString(i.String())
|
||||
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(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 {
|
||||
return nil, catchVipsError()
|
||||
}
|
||||
|
|
@ -331,36 +416,37 @@ func vipsAffine(input *C.struct__VipsImage, residual float64, i Interpolator) (*
|
|||
return image, nil
|
||||
}
|
||||
|
||||
func vipsImageType(buf []byte) ImageType {
|
||||
imageType := UNKNOWN
|
||||
|
||||
if len(buf) == 0 {
|
||||
return imageType
|
||||
func vipsImageType(bytes []byte) ImageType {
|
||||
if len(bytes) == 0 {
|
||||
return UNKNOWN
|
||||
}
|
||||
|
||||
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))
|
||||
imageBuf := unsafe.Pointer(&buf[0])
|
||||
bufferType := C.GoString(C.vips_foreign_find_load_buffer(imageBuf, length))
|
||||
|
||||
switch {
|
||||
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
|
||||
load := C.vips_foreign_find_load_buffer(imageBuf, length)
|
||||
defer C.free(imageBuf)
|
||||
return C.GoString(load)
|
||||
}
|
||||
|
||||
func catchVipsError() error {
|
||||
|
|
@ -369,3 +455,32 @@ func catchVipsError() error {
|
|||
C.vips_thread_shutdown()
|
||||
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/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 {
|
||||
UNKNOWN = 0,
|
||||
JPEG,
|
||||
|
|
@ -12,46 +34,91 @@ enum types {
|
|||
};
|
||||
|
||||
typedef struct {
|
||||
char *Text;
|
||||
char *Font;
|
||||
} watermarkTextOptions;
|
||||
const char *Text;
|
||||
const char *Font;
|
||||
} WatermarkTextOptions;
|
||||
|
||||
typedef struct {
|
||||
int Width;
|
||||
int DPI;
|
||||
int Margin;
|
||||
int NoReplicate;
|
||||
float Opacity;
|
||||
int Width;
|
||||
int DPI;
|
||||
int Margin;
|
||||
int NoReplicate;
|
||||
float Opacity;
|
||||
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
|
||||
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);
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
};
|
||||
}
|
||||
|
||||
int
|
||||
vips_rotate(VipsImage *in, VipsImage **buf, int angle)
|
||||
{
|
||||
vips_rotate(VipsImage *in, VipsImage **out, int angle) {
|
||||
int rotate = VIPS_ANGLE_D0;
|
||||
|
||||
if (angle == 90) {
|
||||
|
|
@ -62,35 +129,21 @@ vips_rotate(VipsImage *in, VipsImage **buf, int angle)
|
|||
rotate = VIPS_ANGLE_D270;
|
||||
}
|
||||
|
||||
return vips_rot(in, buf, rotate, NULL);
|
||||
};
|
||||
return vips_rot(in, out, rotate, NULL);
|
||||
}
|
||||
|
||||
int
|
||||
vips_exif_orientation(VipsImage *image) {
|
||||
int orientation = 0;
|
||||
const char **exif;
|
||||
const char *exif;
|
||||
if (
|
||||
vips_image_get_typeof(image, "exif-ifd0-Orientation") != 0 &&
|
||||
!vips_image_get_string(image, "exif-ifd0-Orientation", exif)
|
||||
vips_image_get_typeof(image, EXIF_IFD0_ORIENTATION) != 0 &&
|
||||
!vips_image_get_string(image, EXIF_IFD0_ORIENTATION, &exif)
|
||||
) {
|
||||
orientation = atoi(exif[0]);
|
||||
orientation = atoi(&exif[0]);
|
||||
}
|
||||
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
|
||||
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);
|
||||
g_object_unref(interpolator);
|
||||
return window_size;
|
||||
};
|
||||
}
|
||||
|
||||
const char *
|
||||
vips_enum_nick_bridge(VipsImage *image) {
|
||||
return vips_enum_nick(VIPS_TYPE_INTERPRETATION, image->Type);
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
};
|
||||
}
|
||||
|
||||
int
|
||||
vips_colorspace_bridge(VipsImage *in, VipsImage **out)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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);
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
};
|
||||
}
|
||||
|
||||
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", interlace, NULL);
|
||||
};
|
||||
vips_colourspace_issupported_bridge(VipsImage *in) {
|
||||
return vips_colourspace_issupported(in) ? 1 : 0;
|
||||
}
|
||||
|
||||
VipsInterpretation
|
||||
vips_image_guess_interpretation_bridge(VipsImage *in) {
|
||||
return vips_image_guess_interpretation(in);
|
||||
}
|
||||
|
||||
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))
|
||||
return vips_pngsave_buffer(in, buf, len, "strip", FALSE, "compression", compression,
|
||||
"interlace", interlace, "filter", VIPS_FOREIGN_PNG_FILTER_NONE, NULL);
|
||||
return vips_pngsave_buffer(in, buf, len,
|
||||
"strip", FALSE,
|
||||
"compression", compression,
|
||||
"interlace", with_interlace(interlace),
|
||||
"filter", VIPS_FOREIGN_PNG_FILTER_NONE,
|
||||
NULL
|
||||
);
|
||||
#else
|
||||
return vips_pngsave_buffer(in, buf, len, "strip", FALSE, "compression", compression,
|
||||
"interlace", interlace, NULL);
|
||||
return vips_pngsave_buffer(in, buf, len,
|
||||
"strip", FALSE,
|
||||
"compression", compression,
|
||||
"interlace", with_interlace(interlace),
|
||||
NULL
|
||||
);
|
||||
#endif
|
||||
};
|
||||
}
|
||||
|
||||
int
|
||||
vips_webpsave_bridge(VipsImage *in, void **buf, size_t *len, int strip, int quality, int interlace)
|
||||
{
|
||||
return vips_webpsave_buffer(in, buf, len, "strip", strip, "Q", quality, "optimize_coding", TRUE, "interlace", interlace, NULL);
|
||||
};
|
||||
vips_webpsave_bridge(VipsImage *in, void **buf, size_t *len, int strip, int quality) {
|
||||
return vips_webpsave_buffer(in, buf, len,
|
||||
"strip", strip,
|
||||
"Q", quality,
|
||||
NULL
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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)
|
||||
} 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
|
||||
}
|
||||
|
||||
return code;
|
||||
};
|
||||
}
|
||||
|
||||
int
|
||||
vips_watermark(VipsImage *in, VipsImage **out, watermarkTextOptions *to, watermarkOptions *o)
|
||||
{
|
||||
double ones[3] = { 1, 1, 1 };
|
||||
vips_watermark_replicate (VipsImage *orig, VipsImage *in, VipsImage **out) {
|
||||
VipsImage *cache = vips_image_new();
|
||||
|
||||
VipsImage *base = vips_image_new();
|
||||
VipsImage **t = (VipsImage **) vips_object_local_array(VIPS_OBJECT(base), 12);
|
||||
t[0] = in;
|
||||
if (
|
||||
vips_replicate(in, &cache,
|
||||
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.
|
||||
if (
|
||||
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);
|
||||
}
|
||||
g_object_unref(cache);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Replicate if necessary
|
||||
if (o->NoReplicate != 1 && (
|
||||
vips_replicate(t[4], &t[5],
|
||||
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);
|
||||
}
|
||||
int
|
||||
vips_watermark(VipsImage *in, VipsImage **out, WatermarkTextOptions *to, WatermarkOptions *o) {
|
||||
double ones[3] = { 1, 1, 1 };
|
||||
|
||||
// Make the constant image to paint the text with.
|
||||
if (
|
||||
vips_black(&t[7], 1, 1, NULL) ||
|
||||
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);
|
||||
}
|
||||
VipsImage *base = vips_image_new();
|
||||
VipsImage **t = (VipsImage **) vips_object_local_array(VIPS_OBJECT(base), 10);
|
||||
t[0] = in;
|
||||
|
||||
// Blend the mask and text and write to output.
|
||||
if (vips_ifthenelse(t[6], t[11], t[0], out, "blend", TRUE, NULL)) {
|
||||
g_object_unref(base);
|
||||
return (1);
|
||||
}
|
||||
// Make the mask.
|
||||
if (
|
||||
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;
|
||||
}
|
||||
|
||||
g_object_unref(base);
|
||||
return (0);
|
||||
};
|
||||
// Replicate if necessary
|
||||
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 {
|
||||
img, _ := os.Open(path.Join("fixtures", file.name))
|
||||
buf, _ := ioutil.ReadAll(img)
|
||||
defer img.Close()
|
||||
|
||||
image, imageType, _ := vipsRead(buf)
|
||||
image, imageType, _ := vipsRead(readImage(file.name))
|
||||
if image == nil {
|
||||
t.Fatal("Empty image")
|
||||
}
|
||||
if imageType != file.expected {
|
||||
t.Fatal("Empty image")
|
||||
t.Fatal("Invalid image type")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestVipsSave(t *testing.T) {
|
||||
img, _ := os.Open(path.Join("fixtures", "test.jpg"))
|
||||
buf, _ := ioutil.ReadAll(img)
|
||||
defer img.Close()
|
||||
|
||||
image, _, _ := vipsRead(buf)
|
||||
if image == nil {
|
||||
t.Fatal("Empty image")
|
||||
}
|
||||
|
||||
options := vipsSaveOptions{Quality: 95, Type: JPEG}
|
||||
image, _, _ := vipsRead(readImage("test.jpg"))
|
||||
options := vipsSaveOptions{Quality: 95, Type: JPEG, Interlace: true}
|
||||
|
||||
buf, err := vipsSave(image, options)
|
||||
if err != nil {
|
||||
|
|
@ -52,3 +40,80 @@ func TestVipsSave(t *testing.T) {
|
|||
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