diff --git a/vendor/manifest b/vendor/manifest index b5725ca36..6e6e24640 100644 --- a/vendor/manifest +++ b/vendor/manifest @@ -179,6 +179,12 @@ "revision": "61e43dc76f7ee59a82bdf3d71033dc12bea4c77d", "branch": "master" }, + { + "importpath": "github.com/tj/go-debug", + "repository": "https://github.com/tj/go-debug", + "revision": "ff4a55a20a86994118644bbddc6a216da193cc13", + "branch": "master" + }, { "importpath": "golang.org/x/crypto/bcrypt", "repository": "https://go.googlesource.com/crypto", @@ -225,6 +231,12 @@ "revision": "bfee1239d796830ca346767650cce5ba90d58c57", "branch": "master" }, + { + "importpath": "gopkg.in/h2non/bimg.v1", + "repository": "https://gopkg.in/h2non/bimg.v1", + "revision": "45f8993550e71ee7b8001d40c681c6c9fa822357", + "branch": "master" + }, { "importpath": "gopkg.in/yaml.v2", "repository": "https://gopkg.in/yaml.v2", diff --git a/vendor/src/github.com/tj/go-debug/History.md b/vendor/src/github.com/tj/go-debug/History.md new file mode 100644 index 000000000..318ceb4d3 --- /dev/null +++ b/vendor/src/github.com/tj/go-debug/History.md @@ -0,0 +1,21 @@ + +v2.0.0 / 2014-10-22 +================== + + * remove live toggling feature. Closes #10 + +1.1.1 / 2014-07-07 +================== + + * fix: dispose socket. Closes #1 + +1.1.0 / 2014-06-29 +================== + + * add unix domain socket live debugging support + * add support for enabling/disabling at runtime + +0.1.0 / 2014-05-24 +================== + + * add global and debug relative deltas diff --git a/vendor/src/github.com/tj/go-debug/Makefile b/vendor/src/github.com/tj/go-debug/Makefile new file mode 100644 index 000000000..16bc6d36f --- /dev/null +++ b/vendor/src/github.com/tj/go-debug/Makefile @@ -0,0 +1,8 @@ + +test: + @go test + +bench: + @go test -bench=. + +.PHONY: bench test \ No newline at end of file diff --git a/vendor/src/github.com/tj/go-debug/Readme.md b/vendor/src/github.com/tj/go-debug/Readme.md new file mode 100644 index 000000000..6560af8a7 --- /dev/null +++ b/vendor/src/github.com/tj/go-debug/Readme.md @@ -0,0 +1,75 @@ + +# go-debug + + Conditional debug logging for Go libraries. + + View the [docs](http://godoc.org/github.com/tj/go-debug). + +## Installation + +``` +$ go get github.com/tj/go-debug +``` + +## Example + +```go +package main + +import . "github.com/tj/go-debug" +import "time" + +var debug = Debug("single") + +func main() { + for { + debug("sending mail") + debug("send email to %s", "tobi@segment.io") + debug("send email to %s", "loki@segment.io") + debug("send email to %s", "jane@segment.io") + time.Sleep(500 * time.Millisecond) + } +} +``` + +If you run the program with the `DEBUG=*` environment variable you will see: + +``` +15:58:15.115 34us 33us single - sending mail +15:58:15.116 3us 3us single - send email to tobi@segment.io +15:58:15.116 1us 1us single - send email to loki@segment.io +15:58:15.116 1us 1us single - send email to jane@segment.io +15:58:15.620 504ms 504ms single - sending mail +15:58:15.620 6us 6us single - send email to tobi@segment.io +15:58:15.620 4us 4us single - send email to loki@segment.io +15:58:15.620 4us 4us single - send email to jane@segment.io +15:58:16.123 503ms 503ms single - sending mail +15:58:16.123 7us 7us single - send email to tobi@segment.io +15:58:16.123 4us 4us single - send email to loki@segment.io +15:58:16.123 4us 4us single - send email to jane@segment.io +15:58:16.625 501ms 501ms single - sending mail +15:58:16.625 4us 4us single - send email to tobi@segment.io +15:58:16.625 4us 4us single - send email to loki@segment.io +15:58:16.625 5us 5us single - send email to jane@segment.io +``` + +A timestamp and two deltas are displayed. The timestamp consists of hour, minute, second and microseconds. The left-most delta is relative to the previous debug call of any name, followed by a delta specific to that debug function. These may be useful to identify timing issues and potential bottlenecks. + +## The DEBUG environment variable + + Executables often support `--verbose` flags for conditional logging, however + libraries typically either require altering your code to enable logging, + or simply omit logging all together. go-debug allows conditional logging + to be enabled via the __DEBUG__ environment variable, where one or more + patterns may be specified. + + For example suppose your application has several models and you want + to output logs for users only, you might use `DEBUG=models:user`. In contrast + if you wanted to see what all database activity was you might use `DEBUG=models:*`, + or if you're love being swamped with logs: `DEBUG=*`. You may also specify a list of names delimited by a comma, for example `DEBUG=mongo,redis:*`. + + The name given _should_ be the package name, however you can use whatever you like. + +# License + +MIT \ No newline at end of file diff --git a/vendor/src/github.com/tj/go-debug/debug.go b/vendor/src/github.com/tj/go-debug/debug.go new file mode 100644 index 000000000..016ca4698 --- /dev/null +++ b/vendor/src/github.com/tj/go-debug/debug.go @@ -0,0 +1,128 @@ +package debug + +import ( + "fmt" + "io" + "math/rand" + "os" + "regexp" + "strconv" + "strings" + "sync" + "time" +) + +var ( + writer io.Writer = os.Stderr + reg *regexp.Regexp + m sync.Mutex + enabled = false +) + +// Debugger function. +type DebugFunction func(string, ...interface{}) + +// Terminal colors used at random. +var colors []string = []string{ + "31", + "32", + "33", + "34", + "35", + "36", +} + +// Initialize with DEBUG environment variable. +func init() { + env := os.Getenv("DEBUG") + + if "" != env { + Enable(env) + } +} + +// SetWriter replaces the default of os.Stderr with `w`. +func SetWriter(w io.Writer) { + m.Lock() + defer m.Unlock() + writer = w +} + +// Disable all pattern matching. This function is thread-safe. +func Disable() { + m.Lock() + defer m.Unlock() + enabled = false +} + +// Enable the given debug `pattern`. Patterns take a glob-like form, +// for example if you wanted to enable everything, just use "*", or +// if you had a library named mongodb you could use "mongodb:connection", +// or "mongodb:*". Multiple matches can be made with a comma, for +// example "mongo*,redis*". +// +// This function is thread-safe. +func Enable(pattern string) { + m.Lock() + defer m.Unlock() + pattern = regexp.QuoteMeta(pattern) + pattern = strings.Replace(pattern, "\\*", ".*?", -1) + pattern = strings.Replace(pattern, ",", "|", -1) + pattern = "^(" + pattern + ")$" + reg = regexp.MustCompile(pattern) + enabled = true +} + +// Debug creates a debug function for `name` which you call +// with printf-style arguments in your application or library. +func Debug(name string) DebugFunction { + prevGlobal := time.Now() + color := colors[rand.Intn(len(colors))] + prev := time.Now() + + return func(format string, args ...interface{}) { + if !enabled { + return + } + + if !reg.MatchString(name) { + return + } + + d := deltas(prevGlobal, prev, color) + fmt.Fprintf(writer, d+" \033["+color+"m"+name+"\033[0m - "+format+"\n", args...) + prevGlobal = time.Now() + prev = time.Now() + } +} + +// Return formatting for deltas. +func deltas(prevGlobal, prev time.Time, color string) string { + now := time.Now() + global := now.Sub(prevGlobal).Nanoseconds() + delta := now.Sub(prev).Nanoseconds() + ts := now.UTC().Format("15:04:05.000") + deltas := fmt.Sprintf("%s %-6s \033["+color+"m%-6s", ts, humanizeNano(global), humanizeNano(delta)) + return deltas +} + +// Humanize nanoseconds to a string. +func humanizeNano(n int64) 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 +} diff --git a/vendor/src/github.com/tj/go-debug/debug_test.go b/vendor/src/github.com/tj/go-debug/debug_test.go new file mode 100644 index 000000000..7ce2764c1 --- /dev/null +++ b/vendor/src/github.com/tj/go-debug/debug_test.go @@ -0,0 +1,152 @@ +package debug + +import "testing" +import "strings" +import "bytes" +import "time" + +func assertContains(t *testing.T, str, substr string) { + if !strings.Contains(str, substr) { + t.Fatalf("expected %q to contain %q", str, substr) + } +} + +func assertNotContains(t *testing.T, str, substr string) { + if strings.Contains(str, substr) { + t.Fatalf("expected %q to not contain %q", str, substr) + } +} + +func TestDefault(t *testing.T) { + var b []byte + buf := bytes.NewBuffer(b) + SetWriter(buf) + + debug := Debug("foo") + debug("something") + debug("here") + debug("whoop") + + if buf.Len() != 0 { + t.Fatalf("buffer should be empty") + } +} + +func TestEnable(t *testing.T) { + var b []byte + buf := bytes.NewBuffer(b) + SetWriter(buf) + + Enable("foo") + + debug := Debug("foo") + debug("something") + debug("here") + debug("whoop") + + if buf.Len() == 0 { + t.Fatalf("buffer should have output") + } + + str := string(buf.Bytes()) + assertContains(t, str, "something") + assertContains(t, str, "here") + assertContains(t, str, "whoop") +} + +func TestMultipleOneEnabled(t *testing.T) { + var b []byte + buf := bytes.NewBuffer(b) + SetWriter(buf) + + Enable("foo") + + foo := Debug("foo") + foo("foo") + + bar := Debug("bar") + bar("bar") + + if buf.Len() == 0 { + t.Fatalf("buffer should have output") + } + + str := string(buf.Bytes()) + assertContains(t, str, "foo") + assertNotContains(t, str, "bar") +} + +func TestMultipleEnabled(t *testing.T) { + var b []byte + buf := bytes.NewBuffer(b) + SetWriter(buf) + + Enable("foo,bar") + + foo := Debug("foo") + foo("foo") + + bar := Debug("bar") + bar("bar") + + if buf.Len() == 0 { + t.Fatalf("buffer should have output") + } + + str := string(buf.Bytes()) + assertContains(t, str, "foo") + assertContains(t, str, "bar") +} + +func TestEnableDisable(t *testing.T) { + var b []byte + buf := bytes.NewBuffer(b) + SetWriter(buf) + + Enable("foo,bar") + Disable() + + foo := Debug("foo") + foo("foo") + + bar := Debug("bar") + bar("bar") + + if buf.Len() != 0 { + t.Fatalf("buffer should not have output") + } +} + +func ExampleEnable() { + Enable("mongo:connection") + Enable("mongo:*") + Enable("foo,bar,baz") + Enable("*") +} + +func ExampleDebug() { + var debug = Debug("single") + + for { + debug("sending mail") + debug("send email to %s", "tobi@segment.io") + debug("send email to %s", "loki@segment.io") + debug("send email to %s", "jane@segment.io") + time.Sleep(500 * time.Millisecond) + } +} + +func BenchmarkDisabled(b *testing.B) { + debug := Debug("something") + for i := 0; i < b.N; i++ { + debug("stuff") + } +} + +func BenchmarkNonMatch(b *testing.B) { + debug := Debug("something") + Enable("nonmatch") + for i := 0; i < b.N; i++ { + debug("stuff") + } +} diff --git a/vendor/src/github.com/tj/go-debug/example/multiple.go b/vendor/src/github.com/tj/go-debug/example/multiple.go new file mode 100644 index 000000000..81c330807 --- /dev/null +++ b/vendor/src/github.com/tj/go-debug/example/multiple.go @@ -0,0 +1,25 @@ +package main + +import . "github.com/visionmedia/go-debug" +import "time" + +var a = Debug("multiple:a") +var b = Debug("multiple:b") +var c = Debug("multiple:c") + +func work(debug DebugFunction, delay time.Duration) { + for { + debug("doing stuff") + time.Sleep(delay) + } +} + +func main() { + q := make(chan bool) + + go work(a, 1000*time.Millisecond) + go work(b, 250*time.Millisecond) + go work(c, 100*time.Millisecond) + + <-q +} diff --git a/vendor/src/github.com/tj/go-debug/example/single.go b/vendor/src/github.com/tj/go-debug/example/single.go new file mode 100644 index 000000000..fccfe33f2 --- /dev/null +++ b/vendor/src/github.com/tj/go-debug/example/single.go @@ -0,0 +1,16 @@ +package main + +import . "github.com/visionmedia/go-debug" +import "time" + +var debug = Debug("single") + +func main() { + for { + debug("sending mail") + debug("send email to %s", "tobi@segment.io") + debug("send email to %s", "loki@segment.io") + debug("send email to %s", "jane@segment.io") + time.Sleep(500 * time.Millisecond) + } +} diff --git a/vendor/src/gopkg.in/h2non/bimg.v1/History.md b/vendor/src/gopkg.in/h2non/bimg.v1/History.md new file mode 100644 index 000000000..57cdfc541 --- /dev/null +++ b/vendor/src/gopkg.in/h2non/bimg.v1/History.md @@ -0,0 +1,85 @@ + +## v1.0.9 / 2017-05-25 + + * Merge pull request #156 from Dynom/SmartCropToGravity + * Adding a test, verifying both ways of enabling SmartCrop work + * Merge pull request #149 from waldophotos/master + * Replacing SmartCrop with a Gravity option + * refactor(docs): v8.4 + * Change for older LIBVIPS versions. `vips_bandjoin_const1` is added in libvips 8.2. + * Second try, watermarking memory issue fix + +## v1.0.8 / 2017-05-18 + + * Merge pull request #145 from greut/smartcrop + * Merge pull request #155 from greut/libvips8.5.5 + * Update libvips to 8.5.5. + * Adding basic smartcrop support. + * Merge pull request #153 from abracadaber/master + * Added Linux Mint 17.3+ distro names + * feat(docs): add new maintainer notice (thanks to @kirillDanshin) + * Merge pull request #152 from greut/libvips85 + * Download latest version of libvips from github. + * Merge pull request #147 from h2non/revert-143-master + * Revert "Fix for memory issue when watermarking images" + * Merge pull request #146 from greut/minor-major + * Merge pull request #143 from waldophotos/master + * Merge pull request #144 from greut/go18 + * Fix tests where minor/major were mixed up + * Enabled go 1.8 builds. + * Fix the unref of images, when image isn't transparent + * Fix for memory issue when watermarking images + * feat(docs): add maintainers sections + * Merge pull request #132 from jaume-pinyol/WATERMARK_SUPPORT + * Add support for image watermarks + * Merge pull request #131 from greut/versions + * Running tests on more specific versions. + * refactor(preinstall.sh): remove deprecation notice + * Update preinstall.sh + * fix(requirements): required libvips 7.42 + * fix(History): typo + * chore(History): add breaking change note + +## v1.0.7 / 13-01-2017 + +- fix(#128): crop image calculation for missing width or height axis. +- feat: add TIFF save output format (**note**: this introduces a minor interface breaking change in `bimg.IsImageTypeSupportedByVips` auxiliary function). + +## v1.0.6 / 12-11-2016 + +- feat(#118): handle 16-bit PNGs. +- feat(#119): adds JPEG2000 file for the type tests. +- feat(#121): test bimg against multiple libvips versions. + +## v1.0.5 / 01-10-2016 + +- feat(#92): support Extend param with optional background. +- fix(#106): allow image area extraction without explicit x/y axis. +- feat(api): add Extend type with `libvips` enum alias. + +## v1.0.4 / 29-09-2016 + +- fix(#111): safe check of magick image type support. + +## v1.0.3 / 28-09-2016 + +- fix(#95): better image type inference and support check. +- fix(background): pass proper background RGB color for PNG image conversion. +- feat(types): validate supported image types by current `libvips` compilation. +- feat(types): consistent SVG image checking. +- feat(api): add public functions `VipsIsTypeSupported()`, `IsImageTypeSupportedByVips()` and `IsSVGImage()`. + +## v1.0.2 / 27-09-2016 + +- feat(#95): support GIF, SVG and PDF formats. +- fix(#108): auto-width and height calculations now round instead of floor. + +## v1.0.1 / 22-06-2016 + +- fix(#90): Do not not dereference the original image a second time. + +## v1.0.0 / 21-04-2016 + +- refactor(api): breaking changes: normalize public members to follow Go naming idioms. +- feat(version): bump to major version. API contract won't be compromised in `v1`. +- feat(docs): add missing inline godoc documentation. diff --git a/vendor/src/gopkg.in/h2non/bimg.v1/LICENSE b/vendor/src/gopkg.in/h2non/bimg.v1/LICENSE new file mode 100644 index 000000000..b28d546c6 --- /dev/null +++ b/vendor/src/gopkg.in/h2non/bimg.v1/LICENSE @@ -0,0 +1,24 @@ +The MIT License + +Copyright (c) Tomas Aparicio and contributors + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/vendor/src/gopkg.in/h2non/bimg.v1/README.md b/vendor/src/gopkg.in/h2non/bimg.v1/README.md new file mode 100644 index 000000000..022f6540c --- /dev/null +++ b/vendor/src/gopkg.in/h2non/bimg.v1/README.md @@ -0,0 +1,347 @@ +# bimg [![Build Status](https://travis-ci.org/h2non/bimg.svg)](https://travis-ci.org/h2non/bimg) [![GoDoc](https://godoc.org/github.com/h2non/bimg?status.svg)](https://godoc.org/github.com/h2non/bimg) [![Go Report Card](http://goreportcard.com/badge/h2non/bimg)](http://goreportcard.com/report/h2non/bimg) [![Coverage Status](https://coveralls.io/repos/github/h2non/bimg/badge.svg?branch=master)](https://coveralls.io/github/h2non/bimg?branch=master) ![License](https://img.shields.io/badge/license-MIT-blue.svg) + +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 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 natively, and optionally TIFF, PDF, GIF and SVG formats if `libvips@8.3+` is compiled with proper library bindings. + +bimg is able to output images as JPEG, PNG and WEBP formats, including transparent conversion across them. + +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. + +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](http://nodejs.org). bimg is used in production environments processing thousands of images per day. + +**v1 notice**: `bimg` introduces some minor breaking changes in `v1` release. +If you're using `gopkg.in`, you can still rely in the `v0` without worrying about API breaking changes. + +`bimg` is currently maintained by [Kirill Danshin](https://github.com/kirillDanshin). + +## Contents + +- [Supported image operations](#supported-image-operations) +- [Prerequisites](#prerequisites) +- [Installation](#installation) +- [Performance](#performance) +- [Benchmark](#benchmark) +- [Examples](#examples) +- [Debugging](#debugging) +- [API](#api) +- [Authors](#authors) +- [Credits](#credits) + +## Supported image operations + +- Resize +- Enlarge +- Crop (including smart crop support) +- 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 + +- [libvips](https://github.com/jcupitt/libvips) 7.42+ or 8+ (8.4+ recommended) +- C compatible compiler such as gcc 4.6+ or clang 3.0+ +- Go 1.3+ + +**Note**: `libvips` v8.3+ is required for GIF, PDF and SVG support. + +## Installation + +```bash +go get -u gopkg.in/h2non/bimg.v1 +``` + +### libvips + +Run the following script as `sudo` (supports OSX, Debian/Ubuntu, Redhat, Fedora, Amazon Linux): +```bash +curl -s https://raw.githubusercontent.com/h2non/bimg/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/h2non/bimg/master/preinstall.sh | sudo bash -s --with-openslide +``` + +The [install script](https://github.com/h2non/bimg/blob/master/preinstall.sh) requires `curl` and `pkg-config`. + +## Performance + +libvips is probably the faster open source solution for image processing. +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) + +## Benchmark + +Tested using Go 1.5.1 and libvips-7.42.3 in OSX i7 2.7Ghz +``` +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 +``` + +## Examples + +```go +import ( + "fmt" + "os" + "gopkg.in/h2non/bimg.v1" +) +``` + +#### Resize + +```go +buffer, err := bimg.Read("image.jpg") +if err != nil { + fmt.Fprintln(os.Stderr, err) +} + +newImage, err := bimg.NewImage(buffer).Resize(800, 600) +if err != nil { + fmt.Fprintln(os.Stderr, err) +} + +size, err := bimg.NewImage(newImage).Size() +if size.Width == 400 && size.Height == 300 { + fmt.Println("The image size is valid") +} + +bimg.Write("new.jpg", newImage) +``` + +#### Rotate + +```go +buffer, err := bimg.Read("image.jpg") +if err != nil { + fmt.Fprintln(os.Stderr, err) +} + +newImage, err := bimg.NewImage(buffer).Rotate(90) +if err != nil { + fmt.Fprintln(os.Stderr, err) +} + +bimg.Write("new.jpg", newImage) +``` + +#### Convert + +```go +buffer, err := bimg.Read("image.jpg") +if err != nil { + fmt.Fprintln(os.Stderr, err) +} + +newImage, err := bimg.NewImage(buffer).Convert(bimg.PNG) +if err != nil { + fmt.Fprintln(os.Stderr, err) +} + +if bimg.NewImage(newImage).Type() == "png" { + fmt.Fprintln(os.Stderr, "The image was converted into 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 + +```go +options := bimg.Options{ + Width: 800, + Height: 600, + Crop: true, + Quality: 95, + Rotate: 180, + Interlace: true, +} + +buffer, err := bimg.Read("image.jpg") +if err != nil { + fmt.Fprintln(os.Stderr, err) +} + +newImage, err := bimg.NewImage(buffer).Process(options) +if err != nil { + fmt.Fprintln(os.Stderr, err) +} + +bimg.Write("new.jpg", newImage) +``` + +#### Watermark + +```go +buffer, err := bimg.Read("image.jpg") +if err != nil { + fmt.Fprintln(os.Stderr, err) +} + +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(watermark) +if err != nil { + fmt.Fprintln(os.Stderr, err) +} + +bimg.Write("new.jpg", newImage) +``` + +#### Fluent interface + +```go +buffer, err := bimg.Read("image.jpg") +if err != nil { + fmt.Fprintln(os.Stderr, err) +} + +image := bimg.NewImage(buffer) + +// first crop image +_, err := image.CropByWidth(300) +if err != nil { + fmt.Fprintln(os.Stderr, err) +} + +// then flip it +newImage, err := image.Flip() +if err != nil { + fmt.Fprintln(os.Stderr, err) +} + +// save the cropped and flipped image +bimg.Write("new.jpg", newImage) +``` + +## Debugging + +Run the process passing the `DEBUG` environment variable +``` +DEBUG=bimg ./app +``` + +Enable libvips traces (note that a lot of data will be written in stdout): +``` +VIPS_TRACE=1 ./app +``` + +You can also dump a core on failure, as [John Cuppit](https://github.com/jcupitt) said: +```c +g_log_set_always_fatal( + G_LOG_FLAG_RECURSION | + G_LOG_FLAG_FATAL | + G_LOG_LEVEL_ERROR | + G_LOG_LEVEL_CRITICAL | + G_LOG_LEVEL_WARNING ); +``` + +Or set the G_DEBUG environment variable: +``` +export G_DEBUG=fatal-warnings,fatal-criticals +``` + +## API + +See [godoc reference](https://godoc.org/github.com/h2non/bimg) for detailed API documentation. + +## Authors + +- [Tomás Aparicio](https://github.com/h2non) - Original author and architect. +- [Kirill Danshin](https://github.com/kirillDanshin) - Maintainer since April 2017. + +## Credits + +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 + +[![views](https://sourcegraph.com/api/repos/github.com/h2non/bimg/.counters/views.svg)](https://sourcegraph.com/github.com/h2non/bimg) diff --git a/vendor/src/gopkg.in/h2non/bimg.v1/file.go b/vendor/src/gopkg.in/h2non/bimg.v1/file.go new file mode 100644 index 000000000..0cbf82aa6 --- /dev/null +++ b/vendor/src/gopkg.in/h2non/bimg.v1/file.go @@ -0,0 +1,15 @@ +package bimg + +import "io/ioutil" + +// Read reads all the content of the given file path +// and returns it as byte buffer. +func Read(path string) ([]byte, error) { + return ioutil.ReadFile(path) +} + +// Write writes the given byte buffer into disk +// to the given file path. +func Write(path string, buf []byte) error { + return ioutil.WriteFile(path, buf, 0644) +} diff --git a/vendor/src/gopkg.in/h2non/bimg.v1/file_test.go b/vendor/src/gopkg.in/h2non/bimg.v1/file_test.go new file mode 100644 index 000000000..2144669af --- /dev/null +++ b/vendor/src/gopkg.in/h2non/bimg.v1/file_test.go @@ -0,0 +1,38 @@ +package bimg + +import ( + "testing" +) + +func TestRead(t *testing.T) { + buf, err := Read("fixtures/test.jpg") + + if err != nil { + t.Errorf("Cannot read the image: %#v", err) + } + + if len(buf) == 0 { + t.Fatal("Empty buffer") + } + + if DetermineImageType(buf) != JPEG { + t.Fatal("Image is not jpeg") + } +} + +func TestWrite(t *testing.T) { + buf, err := Read("fixtures/test.jpg") + + if err != nil { + t.Errorf("Cannot read the image: %#v", err) + } + + if len(buf) == 0 { + t.Fatal("Empty buffer") + } + + err = Write("fixtures/test_write_out.jpg", buf) + if err != nil { + t.Fatalf("Cannot write the file: %#v", err) + } +} diff --git a/vendor/src/gopkg.in/h2non/bimg.v1/fixtures/corrupt.jpg b/vendor/src/gopkg.in/h2non/bimg.v1/fixtures/corrupt.jpg new file mode 100644 index 000000000..e59922201 Binary files /dev/null and b/vendor/src/gopkg.in/h2non/bimg.v1/fixtures/corrupt.jpg differ diff --git a/vendor/src/gopkg.in/h2non/bimg.v1/fixtures/northern_cardinal_bird.jpg b/vendor/src/gopkg.in/h2non/bimg.v1/fixtures/northern_cardinal_bird.jpg new file mode 100644 index 000000000..1bf53853d Binary files /dev/null and b/vendor/src/gopkg.in/h2non/bimg.v1/fixtures/northern_cardinal_bird.jpg differ diff --git a/vendor/src/gopkg.in/h2non/bimg.v1/fixtures/test.gif b/vendor/src/gopkg.in/h2non/bimg.v1/fixtures/test.gif new file mode 100644 index 000000000..7bf290acc Binary files /dev/null and b/vendor/src/gopkg.in/h2non/bimg.v1/fixtures/test.gif differ diff --git a/vendor/src/gopkg.in/h2non/bimg.v1/fixtures/test.jp2 b/vendor/src/gopkg.in/h2non/bimg.v1/fixtures/test.jp2 new file mode 100644 index 000000000..940778fff Binary files /dev/null and b/vendor/src/gopkg.in/h2non/bimg.v1/fixtures/test.jp2 differ diff --git a/vendor/src/gopkg.in/h2non/bimg.v1/fixtures/test.jpg b/vendor/src/gopkg.in/h2non/bimg.v1/fixtures/test.jpg new file mode 100644 index 000000000..f17d2f189 Binary files /dev/null and b/vendor/src/gopkg.in/h2non/bimg.v1/fixtures/test.jpg differ diff --git a/vendor/src/gopkg.in/h2non/bimg.v1/fixtures/test.pdf b/vendor/src/gopkg.in/h2non/bimg.v1/fixtures/test.pdf new file mode 100644 index 000000000..c14cc561f Binary files /dev/null and b/vendor/src/gopkg.in/h2non/bimg.v1/fixtures/test.pdf differ diff --git a/vendor/src/gopkg.in/h2non/bimg.v1/fixtures/test.png b/vendor/src/gopkg.in/h2non/bimg.v1/fixtures/test.png new file mode 100644 index 000000000..d2f059120 Binary files /dev/null and b/vendor/src/gopkg.in/h2non/bimg.v1/fixtures/test.png differ diff --git a/vendor/src/gopkg.in/h2non/bimg.v1/fixtures/test.svg b/vendor/src/gopkg.in/h2non/bimg.v1/fixtures/test.svg new file mode 100644 index 000000000..679edec2e --- /dev/null +++ b/vendor/src/gopkg.in/h2non/bimg.v1/fixtures/test.svg @@ -0,0 +1,725 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/src/gopkg.in/h2non/bimg.v1/fixtures/test.webp b/vendor/src/gopkg.in/h2non/bimg.v1/fixtures/test.webp new file mode 100644 index 000000000..122741b60 Binary files /dev/null and b/vendor/src/gopkg.in/h2non/bimg.v1/fixtures/test.webp differ diff --git a/vendor/src/gopkg.in/h2non/bimg.v1/fixtures/test_gif.jpg b/vendor/src/gopkg.in/h2non/bimg.v1/fixtures/test_gif.jpg new file mode 100644 index 000000000..e69de29bb diff --git a/vendor/src/gopkg.in/h2non/bimg.v1/fixtures/test_icc_prophoto.jpg b/vendor/src/gopkg.in/h2non/bimg.v1/fixtures/test_icc_prophoto.jpg new file mode 100644 index 000000000..ebf7f02b3 Binary files /dev/null and b/vendor/src/gopkg.in/h2non/bimg.v1/fixtures/test_icc_prophoto.jpg differ diff --git a/vendor/src/gopkg.in/h2non/bimg.v1/fixtures/test_issue.jpg b/vendor/src/gopkg.in/h2non/bimg.v1/fixtures/test_issue.jpg new file mode 100644 index 000000000..8348e3878 Binary files /dev/null and b/vendor/src/gopkg.in/h2non/bimg.v1/fixtures/test_issue.jpg differ diff --git a/vendor/src/gopkg.in/h2non/bimg.v1/fixtures/test_pdf.jpg b/vendor/src/gopkg.in/h2non/bimg.v1/fixtures/test_pdf.jpg new file mode 100644 index 000000000..e69de29bb diff --git a/vendor/src/gopkg.in/h2non/bimg.v1/fixtures/test_square.jpg b/vendor/src/gopkg.in/h2non/bimg.v1/fixtures/test_square.jpg new file mode 100644 index 000000000..c69aab42b Binary files /dev/null and b/vendor/src/gopkg.in/h2non/bimg.v1/fixtures/test_square.jpg differ diff --git a/vendor/src/gopkg.in/h2non/bimg.v1/fixtures/test_svg.jpg b/vendor/src/gopkg.in/h2non/bimg.v1/fixtures/test_svg.jpg new file mode 100644 index 000000000..e69de29bb diff --git a/vendor/src/gopkg.in/h2non/bimg.v1/fixtures/transparent.png b/vendor/src/gopkg.in/h2non/bimg.v1/fixtures/transparent.png new file mode 100644 index 000000000..c82d01517 Binary files /dev/null and b/vendor/src/gopkg.in/h2non/bimg.v1/fixtures/transparent.png differ diff --git a/vendor/src/gopkg.in/h2non/bimg.v1/fixtures/vertical.jpg b/vendor/src/gopkg.in/h2non/bimg.v1/fixtures/vertical.jpg new file mode 100644 index 000000000..3e12e65e5 Binary files /dev/null and b/vendor/src/gopkg.in/h2non/bimg.v1/fixtures/vertical.jpg differ diff --git a/vendor/src/gopkg.in/h2non/bimg.v1/image.go b/vendor/src/gopkg.in/h2non/bimg.v1/image.go new file mode 100644 index 000000000..efaffcb73 --- /dev/null +++ b/vendor/src/gopkg.in/h2non/bimg.v1/image.go @@ -0,0 +1,223 @@ +package bimg + +// Image provides a simple method DSL to transform a given image as byte buffer. +type Image struct { + buffer []byte +} + +// NewImage creates a new Image struct with method DSL. +func NewImage(buf []byte) *Image { + return &Image{buf} +} + +// Resize resizes the image to fixed width and height. +func (i *Image) Resize(width, height int) ([]byte, error) { + options := Options{ + Width: width, + Height: height, + Embed: true, + } + return i.Process(options) +} + +// ForceResize resizes 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) +} + +// ResizeAndCrop resizes 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) +} + +// SmartCrop produces a thumbnail aiming at focus on the interesting part. +func (i *Image) SmartCrop(width, height int) ([]byte, error) { + options := Options{ + Width: width, + Height: height, + Crop: true, + Gravity: GravitySmart, + } + return i.Process(options) +} + +// Extract area from the by X/Y axis in the current image. +func (i *Image) Extract(top, left, width, height int) ([]byte, error) { + options := Options{ + Top: top, + Left: left, + AreaWidth: width, + AreaHeight: height, + } + + if top == 0 && left == 0 { + options.Top = -1 + } + + return i.Process(options) +} + +// Enlarge enlarges the image by width and height. Aspect ratio is maintained. +func (i *Image) Enlarge(width, height int) ([]byte, error) { + options := Options{ + Width: width, + Height: height, + Enlarge: true, + } + return i.Process(options) +} + +// EnlargeAndCrop enlarges 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 crops the image to the exact size specified. +func (i *Image) Crop(width, height int, gravity Gravity) ([]byte, error) { + options := Options{ + Width: width, + Height: height, + Gravity: gravity, + Crop: true, + } + return i.Process(options) +} + +// CropByWidth crops an image by width only param (auto height). +func (i *Image) CropByWidth(width int) ([]byte, error) { + options := Options{ + Width: width, + Crop: true, + } + return i.Process(options) +} + +// CropByHeight crops an image by height (auto width). +func (i *Image) CropByHeight(height int) ([]byte, error) { + options := Options{ + Height: height, + Crop: true, + } + return i.Process(options) +} + +// Thumbnail creates a thumbnail of the image by the a given width by aspect ratio 4:4. +func (i *Image) Thumbnail(pixels int) ([]byte, error) { + options := Options{ + Width: pixels, + Height: pixels, + Crop: true, + Quality: 95, + } + return i.Process(options) +} + +// Watermark adds text as watermark on the given image. +func (i *Image) Watermark(w Watermark) ([]byte, error) { + options := Options{Watermark: w} + return i.Process(options) +} + +// WatermarkImage adds image as watermark on the given image. +func (i *Image) WatermarkImage(w WatermarkImage) ([]byte, error) { + options := Options{WatermarkImage: w} + return i.Process(options) +} + +// Zoom zooms 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) +} + +// Rotate rotates the image by given angle degrees (0, 90, 180 or 270). +func (i *Image) Rotate(a Angle) ([]byte, error) { + options := Options{Rotate: a} + return i.Process(options) +} + +// Flip flips the image about the vertical Y axis. +func (i *Image) Flip() ([]byte, error) { + options := Options{Flip: true} + return i.Process(options) +} + +// Flop flops the image about the horizontal X axis. +func (i *Image) Flop() ([]byte, error) { + options := Options{Flop: true} + return i.Process(options) +} + +// Convert converts image to another format. +func (i *Image) Convert(t ImageType) ([]byte, error) { + options := Options{Type: t} + return i.Process(options) +} + +// Colourspace performs a color space conversion bsaed on the given interpretation. +func (i *Image) Colourspace(c Interpretation) ([]byte, error) { + options := Options{Interpretation: c} + return i.Process(options) +} + +// Process processes the image based on the given transformation options, +// talking with libvips bindings accordingly and returning the resultant +// image buffer. +func (i *Image) Process(o Options) ([]byte, error) { + image, err := Resize(i.buffer, o) + if err != nil { + return nil, err + } + i.buffer = image + return image, nil +} + +// Metadata returns the image metadata (size, alpha channel, profile, EXIF rotation). +func (i *Image) Metadata() (ImageMetadata, error) { + return Metadata(i.buffer) +} + +// Interpretation gets 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) +} + +// ColourspaceIsSupported checks if the current image +// color space is supported. +func (i *Image) ColourspaceIsSupported() (bool, error) { + return ColourspaceIsSupported(i.buffer) +} + +// Type returns the image type format (jpeg, png, webp, tiff). +func (i *Image) Type() string { + return DetermineImageTypeName(i.buffer) +} + +// Size returns the image size as form of width and height pixels. +func (i *Image) Size() (ImageSize, error) { + return Size(i.buffer) +} + +// Image returns the current resultant image image buffer. +func (i *Image) Image() []byte { + return i.buffer +} diff --git a/vendor/src/gopkg.in/h2non/bimg.v1/image_test.go b/vendor/src/gopkg.in/h2non/bimg.v1/image_test.go new file mode 100644 index 000000000..96fc4ad9f --- /dev/null +++ b/vendor/src/gopkg.in/h2non/bimg.v1/image_test.go @@ -0,0 +1,496 @@ +package bimg + +import ( + "fmt" + "path" + "testing" +) + +func TestImageResize(t *testing.T) { + buf, err := initImage("test.jpg").Resize(300, 240) + if err != nil { + t.Errorf("Cannot process the image: %#v", err) + } + + err = assertSize(buf, 300, 240) + if err != nil { + t.Error(err) + } + + Write("fixtures/test_resize_out.jpg", buf) +} + +func TestImageGifResize(t *testing.T) { + _, err := initImage("test.gif").Resize(300, 240) + if err == nil { + t.Errorf("GIF shouldn't be saved within VIPS") + } +} + +func TestImagePdfResize(t *testing.T) { + _, err := initImage("test.pdf").Resize(300, 240) + if err == nil { + t.Errorf("PDF cannot be saved within VIPS") + } +} + +func TestImageSvgResize(t *testing.T) { + _, err := initImage("test.svg").Resize(300, 240) + if err == nil { + t.Errorf("SVG cannot be saved within VIPS") + } +} + +func TestImageGifToJpeg(t *testing.T) { + if VipsMajorVersion >= 8 && VipsMinorVersion > 2 { + i := initImage("test.gif") + options := Options{ + Type: JPEG, + } + buf, err := i.Process(options) + if err != nil { + t.Errorf("Cannot process the image: %#v", err) + } + + Write("fixtures/test_gif.jpg", buf) + } +} + +func TestImagePdfToJpeg(t *testing.T) { + if VipsMajorVersion >= 8 && VipsMinorVersion > 2 { + i := initImage("test.pdf") + options := Options{ + Type: JPEG, + } + buf, err := i.Process(options) + if err != nil { + t.Errorf("Cannot process the image: %#v", err) + } + + Write("fixtures/test_pdf.jpg", buf) + } +} + +func TestImageSvgToJpeg(t *testing.T) { + if VipsMajorVersion >= 8 && VipsMinorVersion > 2 { + i := initImage("test.svg") + options := Options{ + Type: JPEG, + } + buf, err := i.Process(options) + if err != nil { + t.Errorf("Cannot process the image: %#v", err) + } + + Write("fixtures/test_svg.jpg", buf) + } +} + +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, 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) + } + + 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 { + t.Errorf("Cannot process the image: %#v", err) + } + + err = assertSize(buf, 500, 375) + if err != nil { + t.Error(err) + } + + 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, GravityNorth) + if err != nil { + t.Errorf("Cannot process the image: %s", err) + } + + err = assertSize(buf, 800, 600) + if err != nil { + t.Error(err) + } + + Write("fixtures/test_crop_out.jpg", buf) +} + +func TestImageCropByWidth(t *testing.T) { + buf, err := initImage("test.jpg").CropByWidth(600) + if err != nil { + t.Errorf("Cannot process the image: %s", err) + } + + err = assertSize(buf, 600, 1050) + if err != nil { + t.Error(err) + } + + Write("fixtures/test_crop_width_out.jpg", buf) +} + +func TestImageCropByHeight(t *testing.T) { + buf, err := initImage("test.jpg").CropByHeight(300) + if err != nil { + t.Errorf("Cannot process the image: %s", err) + } + + err = assertSize(buf, 1680, 300) + if err != nil { + t.Error(err) + } + + Write("fixtures/test_crop_height_out.jpg", buf) +} + +func TestImageThumbnail(t *testing.T) { + buf, err := initImage("test.jpg").Thumbnail(100) + if err != nil { + t.Errorf("Cannot process the image: %s", err) + } + + err = assertSize(buf, 100, 100) + if err != nil { + t.Error(err) + } + + Write("fixtures/test_thumbnail_out.jpg", buf) +} + +func TestImageWatermark(t *testing.T) { + image := initImage("test.jpg") + _, err := image.Crop(800, 600, GravityNorth) + if err != nil { + t.Errorf("Cannot process the image: %#v", err) + } + + buf, err := image.Watermark(Watermark{ + Text: "Copy me if you can", + Opacity: 0.5, + Width: 200, + DPI: 100, + 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_text_out.jpg", buf) +} + +func TestImageWatermarkWithImage(t *testing.T) { + image := initImage("test.jpg") + watermark, _ := imageBuf("transparent.png") + + _, err := image.Crop(800, 600, GravityNorth) + if err != nil { + t.Errorf("Cannot process the image: %#v", err) + } + + buf, err := image.WatermarkImage(WatermarkImage{Left: 100, Top: 100, Buf: watermark}) + + 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_image_out.jpg", buf) +} + +func TestImageWatermarkNoReplicate(t *testing.T) { + image := initImage("test.jpg") + _, err := image.Crop(800, 600, GravityNorth) + if err != nil { + t.Errorf("Cannot process the image: %s", err) + } + + 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) + } + + Write("fixtures/test_zoom_out.jpg", buf) +} + +func TestImageFlip(t *testing.T) { + buf, err := initImage("test.jpg").Flip() + if err != nil { + t.Errorf("Cannot process the image: %#v", err) + } + Write("fixtures/test_flip_out.jpg", buf) +} + +func TestImageFlop(t *testing.T) { + buf, err := initImage("test.jpg").Flop() + if err != nil { + t.Errorf("Cannot process the image: %#v", err) + } + Write("fixtures/test_flop_out.jpg", buf) +} + +func TestImageRotate(t *testing.T) { + buf, err := initImage("test_flip_out.jpg").Rotate(90) + if err != nil { + t.Errorf("Cannot process the image: %#v", err) + } + Write("fixtures/test_image_rotate_out.jpg", buf) +} + +func TestImageConvert(t *testing.T) { + buf, err := initImage("test.jpg").Convert(PNG) + if err != nil { + t.Errorf("Cannot process the image: %#v", err) + } + 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 { + t.Errorf("Cannot process the image: %#v", err) + } + if data.Alpha != true { + t.Fatal("Invalid alpha channel") + } + if data.Size.Width != 400 { + t.Fatal("Invalid width size") + } + if data.Type != "png" { + t.Fatal("Invalid image type") + } +} + +func TestInterpretation(t *testing.T) { + interpretation, err := initImage("test.jpg").Interpretation() + if err != nil { + t.Errorf("Cannot process the image: %#v", err) + } + if interpretation != InterpretationSRGB { + t.Errorf("Invalid interpretation: %d", interpretation) + } +} + +func TestImageColourspace(t *testing.T) { + tests := []struct { + file string + interpretation Interpretation + }{ + {"test.jpg", InterpretationSRGB}, + {"test.jpg", InterpretationBW}, + } + + 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) + if err != nil { + t.Errorf("Cannot process the image: %#v", err) + } + + _, err = image.Flip() + if err != nil { + t.Errorf("Cannot process the image: %#v", err) + } + + _, err = image.Convert(PNG) + if err != nil { + t.Errorf("Cannot process the image: %#v", err) + } + + data, _ := image.Metadata() + if data.Alpha != false { + t.Fatal("Invalid alpha channel") + } + if data.Size.Width != 300 { + t.Fatal("Invalid width size") + } + if data.Type != "png" { + t.Fatal("Invalid image type") + } + + Write("fixtures/test_image_fluent_out.png", image.Image()) +} + +func TestImageSmartCrop(t *testing.T) { + + if !(VipsMajorVersion >= 8 && VipsMinorVersion > 4) { + t.Skipf("Skipping this test, libvips doesn't meet version requirement %s > 8.4", VipsVersion) + } + + i := initImage("northern_cardinal_bird.jpg") + buf, err := i.SmartCrop(300, 300) + if err != nil { + t.Errorf("Cannot process the image: %#v", err) + } + + err = assertSize(buf, 300, 300) + if err != nil { + t.Error(err) + } + + Write("fixtures/test_smart_crop.jpg", buf) +} + +func initImage(file string) *Image { + buf, _ := imageBuf(file) + return NewImage(buf) +} + +func imageBuf(file string) ([]byte, error) { + return Read(path.Join("fixtures", file)) +} + +func assertSize(buf []byte, width, height int) error { + size, err := NewImage(buf).Size() + if err != nil { + return err + } + if size.Width != width || size.Height != height { + return fmt.Errorf("Invalid image size: %dx%d", size.Width, size.Height) + } + return nil +} diff --git a/vendor/src/gopkg.in/h2non/bimg.v1/metadata.go b/vendor/src/gopkg.in/h2non/bimg.v1/metadata.go new file mode 100644 index 000000000..77eac8cd9 --- /dev/null +++ b/vendor/src/gopkg.in/h2non/bimg.v1/metadata.go @@ -0,0 +1,77 @@ +package bimg + +/* +#cgo pkg-config: vips +#include "vips/vips.h" +*/ +import "C" + +// ImageSize represents the image width and height values +type ImageSize struct { + Width int + Height int +} + +// ImageMetadata represents the basic metadata fields +type ImageMetadata struct { + Orientation int + Channels int + Alpha bool + Profile bool + Type string + Space string + Colourspace string + Size ImageSize +} + +// Size returns the image size by width and height pixels. +func Size(buf []byte) (ImageSize, error) { + metadata, err := Metadata(buf) + if err != nil { + return ImageSize{}, err + } + + return ImageSize{ + Width: int(metadata.Size.Width), + Height: int(metadata.Size.Height), + }, nil +} + +// ColourspaceIsSupported checks if the image colourspace is supported by libvips. +func ColourspaceIsSupported(buf []byte) (bool, error) { + return vipsColourspaceIsSupportedBuffer(buf) +} + +// ImageInterpretation returns 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) +} + +// Metadata returns the image metadata (size, type, alpha channel, profile, EXIF orientation...). +func Metadata(buf []byte) (ImageMetadata, error) { + defer C.vips_thread_shutdown() + + image, imageType, err := vipsRead(buf) + if err != nil { + return ImageMetadata{}, err + } + defer C.g_object_unref(C.gpointer(image)) + + size := ImageSize{ + Width: int(image.Xsize), + Height: int(image.Ysize), + } + + metadata := ImageMetadata{ + Size: size, + Channels: int(image.Bands), + Orientation: vipsExifOrientation(image), + Alpha: vipsHasAlpha(image), + Profile: vipsHasProfile(image), + Space: vipsSpace(image), + Type: ImageTypeName(imageType), + } + + return metadata, nil +} diff --git a/vendor/src/gopkg.in/h2non/bimg.v1/metadata_test.go b/vendor/src/gopkg.in/h2non/bimg.v1/metadata_test.go new file mode 100644 index 000000000..663ec5641 --- /dev/null +++ b/vendor/src/gopkg.in/h2non/bimg.v1/metadata_test.go @@ -0,0 +1,124 @@ +package bimg + +import ( + "io/ioutil" + "os" + "path" + "testing" +) + +func TestSize(t *testing.T) { + files := []struct { + name string + width int + height int + }{ + {"test.jpg", 1680, 1050}, + {"test.png", 400, 300}, + {"test.webp", 550, 368}, + } + for _, file := range files { + size, err := Size(readFile(file.name)) + if err != nil { + t.Fatalf("Cannot read the image: %#v", err) + } + + if size.Width != file.width || size.Height != file.height { + t.Fatalf("Unexpected image size: %dx%d", size.Width, size.Height) + } + } +} + +func TestMetadata(t *testing.T) { + files := []struct { + name string + format string + orientation int + alpha bool + profile bool + space string + }{ + {"test.jpg", "jpeg", 0, false, false, "srgb"}, + {"test_icc_prophoto.jpg", "jpeg", 0, false, true, "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: %s -> %s", file.name, err) + } + + if metadata.Type != file.format { + t.Fatalf("Unexpected image format: %s", file.format) + } + if metadata.Orientation != file.orientation { + t.Fatalf("Unexpected image orientation: %d != %d", metadata.Orientation, file.orientation) + } + if metadata.Alpha != file.alpha { + t.Fatalf("Unexpected image alpha: %t != %t", metadata.Alpha, file.alpha) + } + if metadata.Profile != file.profile { + t.Fatalf("Unexpected image profile: %t != %t", metadata.Profile, file.profile) + } + if metadata.Space != file.space { + t.Fatalf("Unexpected image profile: %t != %t", metadata.Profile, file.profile) + } + } +} + +func TestImageInterpretation(t *testing.T) { + files := []struct { + name string + interpretation Interpretation + }{ + {"test.jpg", InterpretationSRGB}, + {"test.png", InterpretationSRGB}, + {"test.webp", InterpretationSRGB}, + } + + 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") + } +} + +func readFile(file string) []byte { + data, _ := os.Open(path.Join("fixtures", file)) + buf, _ := ioutil.ReadAll(data) + return buf +} diff --git a/vendor/src/gopkg.in/h2non/bimg.v1/options.go b/vendor/src/gopkg.in/h2non/bimg.v1/options.go new file mode 100644 index 000000000..f6ebec2c0 --- /dev/null +++ b/vendor/src/gopkg.in/h2non/bimg.v1/options.go @@ -0,0 +1,218 @@ +package bimg + +/* +#cgo pkg-config: vips +#include "vips/vips.h" +*/ +import "C" + +const ( + // Quality defines the default JPEG quality to be used. + Quality = 80 + // MaxSize defines the maximum pixels width or height supported. + MaxSize = 16383 +) + +// Gravity represents the image gravity value. +type Gravity int + +const ( + // GravityCentre represents the centre value used for image gravity orientation. + GravityCentre Gravity = iota + // GravityNorth represents the north value used for image gravity orientation. + GravityNorth + // GravityEast represents the east value used for image gravity orientation. + GravityEast + // GravitySouth represents the south value used for image gravity orientation. + GravitySouth + // GravityWest represents the west value used for image gravity orientation. + GravityWest + // GravitySmart enables libvips Smart Crop algorithm for image gravity orientation. + GravitySmart +) + +// Interpolator represents the image interpolation value. +type Interpolator int + +const ( + // Bicubic interpolation value. + Bicubic Interpolator = iota + // Bilinear interpolation value. + Bilinear + // Nohalo interpolation value. + Nohalo +) + +var interpolations = map[Interpolator]string{ + Bicubic: "bicubic", + Bilinear: "bilinear", + Nohalo: "nohalo", +} + +func (i Interpolator) String() string { + return interpolations[i] +} + +// Angle represents the image rotation angle value. +type Angle int + +const ( + // D0 represents the rotation angle 0 degrees. + D0 Angle = 0 + // D45 represents the rotation angle 90 degrees. + D45 Angle = 45 + // D90 represents the rotation angle 90 degrees. + D90 Angle = 90 + // D135 represents the rotation angle 90 degrees. + D135 Angle = 135 + // D180 represents the rotation angle 180 degrees. + D180 Angle = 180 + // D235 represents the rotation angle 235 degrees. + D235 Angle = 235 + // D270 represents the rotation angle 270 degrees. + D270 Angle = 270 + // D315 represents the rotation angle 180 degrees. + D315 Angle = 315 +) + +// Direction represents the image direction value. +type Direction int + +const ( + // Horizontal represents the orizontal image direction value. + Horizontal Direction = C.VIPS_DIRECTION_HORIZONTAL + // Vertical represents the vertical image direction value. + Vertical Direction = C.VIPS_DIRECTION_VERTICAL +) + +// Interpretation represents the image interpretation type. +// See: http://www.vips.ecs.soton.ac.uk/supported/current/doc/html/libvips/VipsImage.html#VipsInterpretation +type Interpretation int + +const ( + // InterpretationError points to the libvips interpretation error type. + InterpretationError Interpretation = C.VIPS_INTERPRETATION_ERROR + // InterpretationMultiband points to its libvips interpretation equivalent type. + InterpretationMultiband Interpretation = C.VIPS_INTERPRETATION_MULTIBAND + // InterpretationBW points to its libvips interpretation equivalent type. + InterpretationBW Interpretation = C.VIPS_INTERPRETATION_B_W + // InterpretationCMYK points to its libvips interpretation equivalent type. + InterpretationCMYK Interpretation = C.VIPS_INTERPRETATION_CMYK + // InterpretationRGB points to its libvips interpretation equivalent type. + InterpretationRGB Interpretation = C.VIPS_INTERPRETATION_RGB + // InterpretationSRGB points to its libvips interpretation equivalent type. + InterpretationSRGB Interpretation = C.VIPS_INTERPRETATION_sRGB + // InterpretationRGB16 points to its libvips interpretation equivalent type. + InterpretationRGB16 Interpretation = C.VIPS_INTERPRETATION_RGB16 + // InterpretationGREY16 points to its libvips interpretation equivalent type. + InterpretationGREY16 Interpretation = C.VIPS_INTERPRETATION_GREY16 + // InterpretationScRGB points to its libvips interpretation equivalent type. + InterpretationScRGB Interpretation = C.VIPS_INTERPRETATION_scRGB + // InterpretationLAB points to its libvips interpretation equivalent type. + InterpretationLAB Interpretation = C.VIPS_INTERPRETATION_LAB + // InterpretationXYZ points to its libvips interpretation equivalent type. + InterpretationXYZ Interpretation = C.VIPS_INTERPRETATION_XYZ +) + +// Extend represents the image extend mode, used when the edges +// of an image are extended, you can specify how you want the extension done. +// See: http://www.vips.ecs.soton.ac.uk/supported/8.4/doc/html/libvips/libvips-conversion.html#VIPS-EXTEND-BACKGROUND:CAPS +type Extend int + +const ( + // ExtendBlack extend with black (all 0) pixels mode. + ExtendBlack Extend = C.VIPS_EXTEND_BLACK + // ExtendCopy copy the image edges. + ExtendCopy Extend = C.VIPS_EXTEND_COPY + // ExtendRepeat repeat the whole image. + ExtendRepeat Extend = C.VIPS_EXTEND_REPEAT + // ExtendMirror mirror the whole image. + ExtendMirror Extend = C.VIPS_EXTEND_MIRROR + // ExtendWhite extend with white (all bits set) pixels. + ExtendWhite Extend = C.VIPS_EXTEND_WHITE + // ExtendBackground with colour from the background property. + ExtendBackground Extend = C.VIPS_EXTEND_BACKGROUND + // ExtendLast extend with last pixel. + ExtendLast Extend = C.VIPS_EXTEND_LAST +) + +// WatermarkFont defines the default watermark font to be used. +var WatermarkFont = "sans 10" + +// Color represents a traditional RGB color scheme. +type Color struct { + R, G, B uint8 +} + +// ColorBlack is a shortcut to black RGB color representation. +var ColorBlack = Color{0, 0, 0} + +// Watermark represents the text-based watermark supported options. +type Watermark struct { + Width int + DPI int + Margin int + Opacity float32 + NoReplicate bool + Text string + Font string + Background Color +} + +// WatermarkImage represents the image-based watermark supported options. +type WatermarkImage struct { + Left int + Top int + Buf []byte + Opacity float32 +} + +// GaussianBlur represents the gaussian image transformation values. +type GaussianBlur struct { + Sigma float64 + MinAmpl float64 +} + +// Sharpen represents the image sharp transformation options. +type Sharpen struct { + Radius int + X1 float64 + Y2 float64 + Y3 float64 + M1 float64 + M2 float64 +} + +// Options represents the supported image transformation options. +type Options struct { + Height int + Width int + AreaHeight int + AreaWidth int + Top int + Left int + Quality int + Compression int + Zoom int + Crop bool + SmartCrop bool // Deprecated + Enlarge bool + Embed bool + Flip bool + Flop bool + Force bool + NoAutoRotate bool + NoProfile bool + Interlace bool + Extend Extend + Rotate Angle + Background Color + Gravity Gravity + Watermark Watermark + WatermarkImage WatermarkImage + Type ImageType + Interpolator Interpolator + Interpretation Interpretation + GaussianBlur GaussianBlur + Sharpen Sharpen +} diff --git a/vendor/src/gopkg.in/h2non/bimg.v1/preinstall.sh b/vendor/src/gopkg.in/h2non/bimg.v1/preinstall.sh new file mode 100644 index 000000000..47fa24c52 --- /dev/null +++ b/vendor/src/gopkg.in/h2non/bimg.v1/preinstall.sh @@ -0,0 +1,302 @@ +#!/bin/bash + +vips_version_minimum=8.4.2 +vips_version_latest_major_minor=8.4 +vips_version_latest_patch=2 + +openslide_version_minimum=3.4.0 +openslide_version_latest_major_minor=3.4 +openslide_version_latest_patch=1 + +install_libvips_from_source() { + echo "Compiling libvips $vips_version_latest_major_minor.$vips_version_latest_patch from source" + curl -O http://www.vips.ecs.soton.ac.uk/supported/$vips_version_latest_major_minor/vips-$vips_version_latest_major_minor.$vips_version_latest_patch.tar.gz + tar zvxf vips-$vips_version_latest_major_minor.$vips_version_latest_patch.tar.gz + cd vips-$vips_version_latest_major_minor.$vips_version_latest_patch + CXXFLAGS="-D_GLIBCXX_USE_CXX11_ABI=0" ./configure --disable-debug --disable-docs --disable-static --disable-introspection --disable-dependency-tracking --enable-cxx=yes --without-python --without-orc --without-fftw $1 + make + make install + cd .. + rm -rf vips-$vips_version_latest_major_minor.$vips_version_latest_patch + rm vips-$vips_version_latest_major_minor.$vips_version_latest_patch.tar.gz + ldconfig + echo "Installed libvips $(PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig:/usr/lib/pkgconfig pkg-config --modversion vips)" +} + +install_libopenslide_from_source() { + echo "Compiling openslide $openslide_version_latest_major_minor.$openslide_version_latest_patch from source" + curl -O -L https://github.com/openslide/openslide/releases/download/v$openslide_version_latest_major_minor.$openslide_version_latest_patch/openslide-$openslide_version_latest_major_minor.$openslide_version_latest_patch.tar.gz + tar xzvf openslide-$openslide_version_latest_major_minor.$openslide_version_latest_patch.tar.gz + cd openslide-$openslide_version_latest_major_minor.$openslide_version_latest_patch + PKG_CONFIG_PATH=$pkg_config_path ./configure $1 + make + make install + cd .. + rm -rf openslide-$openslide_version_latest_major_minor.$openslide_version_latest_patch + rm openslide-$openslide_version_latest_major_minor.$openslide_version_latest_patch.tar.gz + ldconfig + echo "Installed libopenslide $openslide_version_latest_major_minor.$openslide_version_latest_patch" +} + +sorry() { + echo "Sorry, I don't yet know how to install lib$1 on $2" + exit 1 +} + +pkg_config_path="$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig:/usr/lib/pkgconfig" + +check_if_library_exists() { + PKG_CONFIG_PATH=$pkg_config_path pkg-config --exists $1 + if [ $? -eq 0 ]; then + version_found=$(PKG_CONFIG_PATH=$pkg_config_path pkg-config --modversion $1) + PKG_CONFIG_PATH=$pkg_config_path pkg-config --atleast-version=$2 $1 + if [ $? -eq 0 ]; then + # Found suitable version of libvips + echo "Found lib$1 $version_found" + return 1 + fi + echo "Found lib$1 $version_found but require $2" + else + echo "Could not find lib$1 using a PKG_CONFIG_PATH of '$pkg_config_path'" + fi + return 0 +} + +enable_openslide=0 +# Is libvips already installed, and is it at least the minimum required version? +if [ $# -eq 1 ]; then + if [ "$1" = "--with-openslide" ]; then + echo "Installing vips with openslide support" + enable_openslide=1 + else + echo "Sorry, $1 is not supported. Did you mean --with-openslide?" + exit 1 + fi +fi + +if ! type pkg-config >/dev/null; then + sorry "vips" "a system without pkg-config" +fi + +openslide_exists=0 +if [ $enable_openslide -eq 1 ]; then + check_if_library_exists "openslide" "$openslide_version_minimum" + openslide_exists=$? +fi + +check_if_library_exists "vips" "$vips_version_minimum" +vips_exists=$? +if [ $vips_exists -eq 1 ] && [ $enable_openslide -eq 1 ]; then + if [ $openslide_exists -eq 1 ]; then + # Check if vips compiled with openslide support + vips_with_openslide=`vips list classes | grep -i opensli` + if [ -z $vips_with_openslide ]; then + echo "Vips compiled without openslide support." + else + exit 0 + fi + fi +elif [ $vips_exists -eq 1 ] && [ $enable_openslide -eq 0 ]; then + exit 0 +fi + +# Verify root/sudo access +if [ "$(id -u)" -ne "0" ]; then + echo "Sorry, I need root/sudo access to continue" + exit 1 +fi + +# Deprecation warning +if [ "$(arch)" == "x86_64" ]; then + echo "This script is no longer required on most 64-bit Linux systems when using sharp v0.12.0+" +fi + +# OS-specific installations of libopenslide follows +# Either openslide does not exist, or vips is installed without openslide support +if [ $enable_openslide -eq 1 ] && [ -z $vips_with_openslide ] && [ $openslide_exists -eq 0 ]; then + if [ -f /etc/debian_version ]; then + # Debian Linux + DISTRO=$(lsb_release -c -s) + echo "Detected Debian Linux '$DISTRO'" + case "$DISTRO" in + jessie|vivid|wily|xenial) + # Debian 8, Ubuntu 15 + echo "Installing libopenslide via apt-get" + apt-get install -y libopenslide-dev + ;; + trusty|utopic|qiana|rebecca|rafaela|freya|rosa|sarah|serena) + # Ubuntu 14, Mint 17+ + echo "Installing libopenslide dependencies via apt-get" + apt-get install -y automake build-essential curl zlib1g-dev libopenjpeg-dev libpng12-dev libjpeg-dev libtiff5-dev libgdk-pixbuf2.0-dev libxml2-dev libsqlite3-dev libcairo2-dev libglib2.0-dev sqlite3 libsqlite3-dev + install_libopenslide_from_source + ;; + precise|wheezy|maya) + # Debian 7, Ubuntu 12.04, Mint 13 + echo "Installing libopenslide dependencies via apt-get" + apt-get install -y automake build-essential curl zlib1g-dev libopenjpeg-dev libpng12-dev libjpeg-dev libtiff5-dev libgdk-pixbuf2.0-dev libxml2-dev libsqlite3-dev libcairo2-dev libglib2.0-dev sqlite3 libsqlite3-dev + install_libopenslide_from_source + ;; + *) + # Unsupported Debian-based OS + sorry "openslide" "Debian-based $DISTRO" + ;; + esac + elif [ -f /etc/redhat-release ]; then + # Red Hat Linux + RELEASE=$(cat /etc/redhat-release) + echo "Detected Red Hat Linux '$RELEASE'" + case $RELEASE in + "Red Hat Enterprise Linux release 7."*|"CentOS Linux release 7."*|"Scientific Linux release 7."*) + # RHEL/CentOS 7 + echo "Installing libopenslide dependencies via yum" + yum groupinstall -y "Development Tools" + yum install -y tar curl libpng-devel libjpeg-devel libxml2-devel zlib-devel openjpeg-devel libtiff-devel gdk-pixbuf2-devel sqlite-devel cairo-devel glib2-devel + install_libopenslide_from_source "--prefix=/usr" + ;; + "Red Hat Enterprise Linux release 6."*|"CentOS release 6."*|"Scientific Linux release 6."*) + # RHEL/CentOS 6 + echo "Installing libopenslide dependencies via yum" + yum groupinstall -y "Development Tools" + yum install -y tar curl libpng-devel libjpeg-devel libxml2-devel zlib-devel openjpeg-devel libtiff-devel gdk-pixbuf2-devel sqlite-devel cairo-devel glib2-devel + install_libopenslide_from_source "--prefix=/usr" + ;; + "Fedora release 21 "*|"Fedora release 22 "*) + # Fedora 21, 22 + echo "Installing libopenslide via yum" + yum install -y openslide-devel + ;; + *) + # Unsupported RHEL-based OS + sorry "openslide" "$RELEASE" + ;; + esac + elif [ -f /etc/os-release ]; then + RELEASE=$(cat /etc/os-release | grep VERSION) + echo "Detected OpenSuse Linux '$RELEASE'" + case $RELEASE in + *"13.2"*) + echo "Installing libopenslide via zypper" + zypper --gpg-auto-import-keys install -y libopenslide-devel + ;; + esac + elif [ -f /etc/SuSE-brand ]; then + RELEASE=$(cat /etc/SuSE-brand | grep VERSION) + echo "Detected OpenSuse Linux '$RELEASE'" + case $RELEASE in + *"13.1") + echo "Installing libopenslide dependencies via zypper" + zypper --gpg-auto-import-keys install -y --type pattern devel_basis + zypper --gpg-auto-import-keys install -y tar curl libpng16-devel libjpeg-turbo libjpeg8-devel libxml2-devel zlib-devel openjpeg-devel libtiff-devel libgdk_pixbuf-2_0-0 sqlite3-devel cairo-devel glib2-devel + install_libopenslide_from_source + ;; + esac + else + # Unsupported OS + sorry "openslide" "$(uname -a)" + fi +fi + +# OS-specific installations of libvips follows + +if [ -f /etc/debian_version ]; then + # Debian Linux + DISTRO=$(lsb_release -c -s) + echo "Detected Debian Linux '$DISTRO'" + case "$DISTRO" in + jessie|trusty|utopic|vivid|wily|xenial|qiana|rebecca|rafaela|freya|rosa|sarah|serena) + # Debian 8, Ubuntu 14.04+, Mint 17+ + echo "Installing libvips dependencies via apt-get" + apt-get install -y automake build-essential gobject-introspection gtk-doc-tools libglib2.0-dev libjpeg-dev libpng12-dev libwebp-dev libtiff5-dev libexif-dev libgsf-1-dev liblcms2-dev libxml2-dev swig libmagickcore-dev curl + install_libvips_from_source + ;; + precise|wheezy|maya) + # Debian 7, Ubuntu 12.04, Mint 13 + echo "Installing libvips dependencies via apt-get" + add-apt-repository -y ppa:lyrasis/precise-backports + apt-get update + apt-get install -y automake build-essential gobject-introspection gtk-doc-tools libglib2.0-dev libjpeg-dev libpng12-dev libwebp-dev libtiff4-dev libexif-dev libgsf-1-dev liblcms2-dev libxml2-dev swig libmagickcore-dev curl + install_libvips_from_source + ;; + *) + # Unsupported Debian-based OS + sorry "vips" "Debian-based $DISTRO" + ;; + esac +elif [ -f /etc/redhat-release ]; then + # Red Hat Linux + RELEASE=$(cat /etc/redhat-release) + echo "Detected Red Hat Linux '$RELEASE'" + case $RELEASE in + "Red Hat Enterprise Linux release 7."*|"CentOS Linux release 7."*|"Scientific Linux release 7."*) + # RHEL/CentOS 7 + echo "Installing libvips dependencies via yum" + yum groupinstall -y "Development Tools" + yum install -y tar curl gtk-doc libxml2-devel libjpeg-turbo-devel libpng-devel libtiff-devel libexif-devel libgsf-devel lcms2-devel ImageMagick-devel gobject-introspection-devel libwebp-devel + install_libvips_from_source "--prefix=/usr" + ;; + "Red Hat Enterprise Linux release 6."*|"CentOS release 6."*|"Scientific Linux release 6."*) + # RHEL/CentOS 6 + echo "Installing libvips dependencies via yum" + yum groupinstall -y "Development Tools" + yum install -y tar curl gtk-doc libxml2-devel libjpeg-turbo-devel libpng-devel libtiff-devel libexif-devel libgsf-devel lcms-devel ImageMagick-devel + yum install -y http://li.nux.ro/download/nux/dextop/el6/x86_64/nux-dextop-release-0-2.el6.nux.noarch.rpm + yum install -y --enablerepo=nux-dextop gobject-introspection-devel + yum install -y http://rpms.famillecollet.com/enterprise/remi-release-6.rpm + yum install -y --enablerepo=remi libwebp-devel + install_libvips_from_source "--prefix=/usr" + ;; + "Fedora"*) + # Fedora 21, 22, 23 + echo "Installing libvips dependencies via yum" + yum groupinstall -y "Development Tools" + yum install -y gcc-c++ gtk-doc libxml2-devel libjpeg-turbo-devel libpng-devel libtiff-devel libexif-devel lcms-devel ImageMagick-devel gobject-introspection-devel libwebp-devel curl + install_libvips_from_source "--prefix=/usr" + ;; + *) + # Unsupported RHEL-based OS + sorry "vips" "$RELEASE" + ;; + esac +elif [ -f /etc/system-release ]; then + # Probably Amazon Linux + RELEASE=$(cat /etc/system-release) + case $RELEASE in + "Amazon Linux AMI release 2015.03"|"Amazon Linux AMI release 2015.09") + # Amazon Linux + echo "Detected '$RELEASE'" + echo "Installing libvips dependencies via yum" + yum groupinstall -y "Development Tools" + yum install -y gtk-doc libxml2-devel libjpeg-turbo-devel libpng-devel libtiff-devel libexif-devel libgsf-devel lcms2-devel ImageMagick-devel gobject-introspection-devel libwebp-devel curl + install_libvips_from_source "--prefix=/usr" + ;; + *) + # Unsupported Amazon Linux version + sorry "vips" "$RELEASE" + ;; + esac +elif [ -f /etc/os-release ]; then + RELEASE=$(cat /etc/os-release | grep VERSION) + echo "Detected OpenSuse Linux '$RELEASE'" + case $RELEASE in + *"13.2"*) + echo "Installing libvips dependencies via zypper" + zypper --gpg-auto-import-keys install -y --type pattern devel_basis + zypper --gpg-auto-import-keys install -y tar curl gtk-doc libxml2-devel libjpeg-turbo libjpeg8-devel libpng16-devel libtiff-devel libexif-devel liblcms2-devel ImageMagick-devel gobject-introspection-devel libwebp-devel + install_libvips_from_source + ;; + esac +elif [ -f /etc/SuSE-brand ]; then + RELEASE=$(cat /etc/SuSE-brand | grep VERSION) + echo "Detected OpenSuse Linux '$RELEASE'" + case $RELEASE in + *"13.1") + echo "Installing libvips dependencies via zypper" + zypper --gpg-auto-import-keys install -y --type pattern devel_basis + zypper --gpg-auto-import-keys install -y tar curl gtk-doc libxml2-devel libjpeg-turbo libjpeg8-devel libpng16-devel libtiff-devel libexif-devel liblcms2-devel ImageMagick-devel gobject-introspection-devel libwebp-devel + install_libvips_from_source + ;; + esac +else + # Unsupported OS + sorry "vips" "$(uname -a)" +fi diff --git a/vendor/src/gopkg.in/h2non/bimg.v1/resize.go b/vendor/src/gopkg.in/h2non/bimg.v1/resize.go new file mode 100644 index 000000000..93d624805 --- /dev/null +++ b/vendor/src/gopkg.in/h2non/bimg.v1/resize.go @@ -0,0 +1,561 @@ +package bimg + +/* +#cgo pkg-config: vips +#include "vips/vips.h" +*/ +import "C" + +import ( + "errors" + "math" +) + +// Resize is used to transform a given image as byte buffer +// with the passed options. +func Resize(buf []byte, o Options) ([]byte, error) { + defer C.vips_thread_shutdown() + + image, imageType, err := loadImage(buf) + + if err != nil { + return nil, err + } + + // Clone and define default options + o = applyDefaults(o, imageType) + + if !IsTypeSupported(o.Type) { + return nil, errors.New("Unsupported image output type") + } + + debug("Options: %#v", o) + + // Auto rotate image based on EXIF orientation header + image, rotated, err := rotateAndFlipImage(image, o) + if err != nil { + return nil, err + } + + // If JPEG image, retrieve the buffer + if rotated && imageType == JPEG && !o.NoAutoRotate { + buf, err = getImageBuffer(image) + if err != nil { + return nil, err + } + } + + 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 := 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 && !o.Force { + if inWidth < o.Width && inHeight < o.Height { + factor = 1.0 + shrink = 1 + residual = 0 + o.Width = inWidth + o.Height = inHeight + } + } + + // Try to use libjpeg shrink-on-load + if imageType == JPEG && shrink >= 2 { + tmpImage, factor, err := shrinkJpegImage(buf, image, factor, shrink) + if err != nil { + return nil, err + } + + image = tmpImage + factor = math.Max(factor, 1.0) + shrink = int(math.Floor(factor)) + residual = float64(shrink) / factor + } + + // Zoom image, if necessary + image, err = zoomImage(image, o.Zoom) + if err != nil { + return nil, err + } + + // Transform image, if necessary + if shouldTransformImage(o, inWidth, inHeight) { + image, err = transformImage(image, o, shrink, residual) + if err != nil { + return nil, err + } + } + + // Apply effects, if necessary + if shouldApplyEffects(o) { + image, err = applyEffects(image, o) + if err != nil { + return nil, err + } + } + + // Add watermark, if necessary + image, err = watermarkImageWithText(image, o.Watermark) + if err != nil { + return nil, err + } + + // Add watermark, if necessary + image, err = watermarkImageWithAnotherImage(image, o.WatermarkImage) + if err != nil { + return nil, err + } + + // Flatten image on a background, if necessary + image, err = imageFlatten(image, imageType, o) + if err != nil { + return nil, err + } + + return saveImage(image, o) +} + +func loadImage(buf []byte) (*C.VipsImage, ImageType, error) { + if len(buf) == 0 { + return nil, JPEG, errors.New("Image buffer is empty") + } + + image, imageType, err := vipsRead(buf) + if err != nil { + return nil, JPEG, err + } + + return image, imageType, nil +} + +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 = InterpretationSRGB + } + return o +} + +func saveImage(image *C.VipsImage, o Options) ([]byte, error) { + 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 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 + inWidth := int(image.Xsize) + inHeight := int(image.Ysize) + + switch { + case o.Gravity == GravitySmart, o.SmartCrop: + image, err = vipsSmartCrop(image, o.Width, o.Height) + break + case o.Crop: + 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, o.Background) + break + + case o.Top != 0 || o.Left != 0 || o.AreaWidth != 0 || o.AreaHeight != 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 rotateAndFlipImage(image *C.VipsImage, o Options) (*C.VipsImage, bool, error) { + var err error + var rotated bool + var direction Direction = -1 + + if o.NoAutoRotate == false { + rotation, flip := calculateRotationAndFlip(image, o.Rotate) + if flip { + o.Flip = flip + } + if rotation > 0 && o.Rotate == 0 { + o.Rotate = rotation + } + } + + if o.Rotate > 0 { + rotated = true + image, err = vipsRotate(image, getAngle(o.Rotate)) + } + + if o.Flip { + direction = Horizontal + } else if o.Flop { + direction = Vertical + } + + if direction != -1 { + rotated = true + image, err = vipsFlip(image, direction) + } + + return image, rotated, err +} + +func watermarkImageWithText(image *C.VipsImage, w Watermark) (*C.VipsImage, error) { + if w.Text == "" { + return image, nil + } + + // Defaults + if w.Font == "" { + w.Font = WatermarkFont + } + if w.Width == 0 { + w.Width = int(math.Floor(float64(image.Xsize / 6))) + } + if w.DPI == 0 { + w.DPI = 150 + } + if w.Margin == 0 { + w.Margin = w.Width + } + if w.Opacity == 0 { + w.Opacity = 0.25 + } else if w.Opacity > 1 { + w.Opacity = 1 + } + + image, err := vipsWatermark(image, w) + if err != nil { + return nil, err + } + + return image, nil +} + +func watermarkImageWithAnotherImage(image *C.VipsImage, w WatermarkImage) (*C.VipsImage, error) { + + if len(w.Buf) == 0 { + return image, nil + } + + if w.Opacity == 0.0 { + w.Opacity = 1.0 + } + + image, err := vipsDrawWatermark(image, w) + + if err != nil { + return nil, err + } + + return image, nil +} + +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.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 { + return nil, 0, err + } + + // Recalculate residual float based on dimensions of required vs shrunk images + residualx := float64(o.Width) / float64(image.Xsize) + residualy := float64(o.Height) / float64(image.Ysize) + + if o.Crop { + residual = math.Max(residualx, residualy) + } else { + residual = math.Min(residualx, residualy) + } + + return image, residual, nil +} + +func shrinkJpegImage(buf []byte, input *C.VipsImage, factor float64, shrink int) (*C.VipsImage, float64, error) { + var image *C.VipsImage + var err error + shrinkOnLoad := 1 + + // Recalculate integral shrink and double residual + switch { + case shrink >= 8: + factor = factor / 8 + shrinkOnLoad = 8 + case shrink >= 4: + factor = factor / 4 + shrinkOnLoad = 4 + case shrink >= 2: + factor = factor / 2 + shrinkOnLoad = 2 + } + + // Reload input using shrink-on-load + if shrinkOnLoad > 1 { + image, err = vipsShrinkJpeg(buf, input, shrinkOnLoad) + } + + return image, factor, err +} + +func imageCalculations(o *Options, inWidth, inHeight int) float64 { + factor := 1.0 + xfactor := float64(inWidth) / float64(o.Width) + yfactor := float64(inHeight) / float64(o.Height) + + switch { + // Fixed width and height + case o.Width > 0 && o.Height > 0: + if o.Crop { + factor = math.Min(xfactor, yfactor) + } else { + factor = math.Max(xfactor, yfactor) + } + // Fixed width, auto height + case o.Width > 0: + if o.Crop { + o.Height = inHeight + } else { + factor = xfactor + o.Height = roundFloat(float64(inHeight) / factor) + } + // Fixed height, auto width + case o.Height > 0: + if o.Crop { + o.Width = inWidth + } else { + factor = yfactor + o.Width = roundFloat(float64(inWidth) / factor) + } + // Identity transform + default: + o.Width = inWidth + o.Height = inHeight + break + } + + return factor +} + +func roundFloat(f float64) int { + if f < 0 { + return int(math.Ceil(f - 0.5)) + } + return int(math.Floor(f + 0.5)) +} + +func calculateCrop(inWidth, inHeight, outWidth, outHeight int, gravity Gravity) (int, int) { + left, top := 0, 0 + + switch gravity { + case GravityNorth: + left = (inWidth - outWidth + 1) / 2 + case GravityEast: + left = inWidth - outWidth + top = (inHeight - outHeight + 1) / 2 + case GravitySouth: + left = (inWidth - outWidth + 1) / 2 + top = inHeight - outHeight + case GravityWest: + top = (inHeight - outHeight + 1) / 2 + default: + left = (inWidth - outWidth + 1) / 2 + top = (inHeight - outHeight + 1) / 2 + } + + return left, top +} + +func calculateRotationAndFlip(image *C.VipsImage, angle Angle) (Angle, bool) { + rotate := D0 + flip := false + + 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 { + angle = angle - divisor + } + return Angle(math.Min(float64(angle), 270)) +} diff --git a/vendor/src/gopkg.in/h2non/bimg.v1/resize_test.go b/vendor/src/gopkg.in/h2non/bimg.v1/resize_test.go new file mode 100644 index 000000000..28e310954 --- /dev/null +++ b/vendor/src/gopkg.in/h2non/bimg.v1/resize_test.go @@ -0,0 +1,644 @@ +package bimg + +import ( + "bytes" + "crypto/md5" + "image" + "image/jpeg" + "io/ioutil" + "os" + "path" + "strconv" + "testing" +) + +func TestResize(t *testing.T) { + options := Options{Width: 800, Height: 600} + buf, _ := Read("fixtures/test.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") + } + + 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 TestResizeVerticalImage(t *testing.T) { + tests := []struct { + format ImageType + options Options + }{ + {JPEG, Options{Width: 800, Height: 600}}, + {JPEG, Options{Width: 1000, Height: 1000}}, + {JPEG, Options{Width: 1000, Height: 1500}}, + {JPEG, Options{Width: 1000}}, + {JPEG, Options{Height: 1500}}, + {JPEG, Options{Width: 100, Height: 50}}, + {JPEG, Options{Width: 2000, Height: 2000}}, + {JPEG, Options{Width: 500, Height: 1000}}, + {JPEG, Options{Width: 500}}, + {JPEG, Options{Height: 500}}, + {JPEG, Options{Crop: true, Width: 500, Height: 1000}}, + {JPEG, Options{Crop: true, Enlarge: true, Width: 2000, Height: 1400}}, + {JPEG, Options{Enlarge: true, Force: true, Width: 2000, Height: 2000}}, + {JPEG, Options{Force: true, Width: 2000, Height: 2000}}, + } + + buf, _ := Read("fixtures/vertical.jpg") + for _, test := range tests { + 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.Fatalf("Image format is invalid. Expected: %#v", 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) + } + + Write("fixtures/test_vertical_"+strconv.Itoa(test.options.Width)+"x"+strconv.Itoa(test.options.Height)+"_out.jpg", image) + } +} + +func TestResizeCustomSizes(t *testing.T) { + tests := []struct { + format ImageType + options Options + }{ + {JPEG, Options{Width: 800, Height: 600}}, + {JPEG, Options{Width: 1000, Height: 1000}}, + {JPEG, Options{Width: 100, Height: 50}}, + {JPEG, Options{Width: 2000, Height: 2000}}, + {JPEG, Options{Width: 500, Height: 1000}}, + {JPEG, Options{Width: 500}}, + {JPEG, Options{Height: 500}}, + {JPEG, Options{Crop: true, Width: 500, Height: 1000}}, + {JPEG, Options{Crop: true, Enlarge: true, Width: 2000, Height: 1400}}, + {JPEG, Options{Enlarge: true, Force: true, Width: 2000, Height: 2000}}, + {JPEG, Options{Force: true, Width: 2000, Height: 2000}}, + } + + buf, _ := Read("fixtures/test.jpg") + for _, test := range tests { + 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.Fatalf("Image format is invalid. Expected: %#v", 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) + } + } +} + +func TestResizePrecision(t *testing.T) { + // see https://github.com/h2non/bimg/issues/99 + img := image.NewGray16(image.Rect(0, 0, 1920, 1080)) + input := &bytes.Buffer{} + jpeg.Encode(input, img, nil) + + opts := Options{Width: 300} + newImg, err := Resize(input.Bytes(), opts) + if err != nil { + t.Fatalf("Resize(imgData, %#v) error: %#v", opts, err) + } + + size, _ := Size(newImg) + if size.Width != opts.Width { + t.Fatalf("Invalid width: %d", size.Width) + } +} + +func TestRotate(t *testing.T) { + options := Options{Width: 800, Height: 600, Rotate: 270, Crop: true} + buf, _ := Read("fixtures/test.jpg") + + newImg, err := Resize(buf, options) + if err != nil { + t.Errorf("Resize(imgData, %#v) error: %#v", options, err) + } + + if DetermineImageType(newImg) != JPEG { + t.Error("Image is not jpeg") + } + + size, _ := Size(newImg) + if size.Width != options.Width || size.Height != options.Height { + t.Errorf("Invalid image size: %dx%d", size.Width, size.Height) + } + + Write("fixtures/test_rotate_out.jpg", newImg) +} + +func TestInvalidRotateDegrees(t *testing.T) { + options := Options{Width: 800, Height: 600, Rotate: 111, Crop: true} + buf, _ := Read("fixtures/test.jpg") + + newImg, err := Resize(buf, options) + if err != nil { + t.Errorf("Resize(imgData, %#v) error: %#v", options, err) + } + + if DetermineImageType(newImg) != JPEG { + t.Errorf("Image is not jpeg") + } + + size, _ := Size(newImg) + if size.Width != options.Width || size.Height != options.Height { + t.Errorf("Invalid image size: %dx%d", size.Width, size.Height) + } + + Write("fixtures/test_rotate_invalid_out.jpg", newImg) +} + +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") + } + + 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_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.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 TestEmbedExtendColor(t *testing.T) { + options := Options{Width: 400, Height: 600, Crop: false, Embed: true, Extend: ExtendWhite, Background: Color{255, 20, 10}} + buf, _ := Read("fixtures/test_issue.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_extend_white_out.jpg", newImg) +} + +func TestEmbedExtendWithCustomColor(t *testing.T) { + options := Options{Width: 400, Height: 600, Crop: false, Embed: true, Extend: 5, Background: Color{255, 20, 10}} + buf, _ := Read("fixtures/test_issue.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_extend_background_out.jpg", newImg) +} + +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_out.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_out.jpg", newImg) +} + +func TestExtractWithDefaultAxis(t *testing.T) { + options := Options{AreaWidth: 200, AreaHeight: 200} + 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.AreaHeight || size.Width != options.AreaWidth { + t.Fatalf("Invalid image size: %dx%d", size.Width, size.Height) + } + + Write("fixtures/test_extract_defaults_out.jpg", newImg) +} + +func TestExtractCustomAxis(t *testing.T) { + options := Options{Top: 100, Left: 100, AreaWidth: 200, AreaHeight: 200} + 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.AreaHeight || size.Width != options.AreaWidth { + t.Fatalf("Invalid image size: %dx%d", size.Width, size.Height) + } + + Write("fixtures/test_extract_custom_axis_out.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) + } + } + } +} + +func TestResizePngWithTransparency(t *testing.T) { + width, height := 300, 240 + + options := Options{Width: width, Height: height, Crop: true} + img, err := os.Open("fixtures/transparent.png") + if err != nil { + t.Fatal(err) + } + defer img.Close() + + buf, err := ioutil.ReadAll(img) + if err != nil { + t.Fatal(err) + } + + newImg, err := Resize(buf, options) + if err != nil { + t.Errorf("Resize(imgData, %#v) error: %#v", options, err) + } + + if DetermineImageType(newImg) != PNG { + t.Fatal("Image is not png") + } + + size, _ := Size(newImg) + if size.Height != height || size.Width != width { + t.Fatal("Invalid image size") + } + + Write("fixtures/transparent_out.png", newImg) +} + +func TestIfBothSmartCropOptionsAreIdentical(t *testing.T) { + if !(VipsMajorVersion >= 8 && VipsMinorVersion > 4) { + t.Skipf("Skipping this test, libvips doesn't meet version requirement %s > 8.4", VipsVersion) + } + + benchmarkOptions := Options{Width: 100, Height: 100, Crop: true} + smartCropOptions := Options{Width: 100, Height: 100, Crop: true, SmartCrop: true} + gravityOptions := Options{Width: 100, Height: 100, Crop: true, Gravity: GravitySmart} + + testImg, err := os.Open("fixtures/northern_cardinal_bird.jpg") + if err != nil { + t.Fatal(err) + } + defer testImg.Close() + + testImgByte, err := ioutil.ReadAll(testImg) + if err != nil { + t.Fatal(err) + } + + scImg, err := Resize(testImgByte, smartCropOptions) + if err != nil { + t.Fatal(err) + } + + gImg, err := Resize(testImgByte, gravityOptions) + if err != nil { + t.Fatal(err) + } + + benchmarkImg, err := Resize(testImgByte, benchmarkOptions) + if err != nil { + t.Fatal(err) + } + + sch, gh, bh := md5.Sum(scImg), md5.Sum(gImg), md5.Sum(benchmarkImg) + if gh == bh || sch == bh { + t.Error("Expected both options produce a different result from a standard crop.") + } + + if sch != gh { + t.Errorf("Expected both options to result in the same output, %x != %x", sch, gh) + } +} + +func runBenchmarkResize(file string, o Options, b *testing.B) { + buf, _ := Read(path.Join("fixtures", file)) + + for n := 0; n < b.N; n++ { + Resize(buf, o) + } +} + +func BenchmarkRotateJpeg(b *testing.B) { + options := Options{Rotate: 180} + runBenchmarkResize("test.jpg", options, b) +} + +func BenchmarkResizeLargeJpeg(b *testing.B) { + options := Options{ + Width: 800, + Height: 600, + } + runBenchmarkResize("test.jpg", options, b) +} + +func BenchmarkResizePng(b *testing.B) { + options := Options{ + Width: 200, + Height: 200, + } + runBenchmarkResize("test.png", options, b) +} + +func BenchmarkResizeWebP(b *testing.B) { + options := Options{ + Width: 200, + Height: 200, + } + runBenchmarkResize("test.webp", options, b) +} + +func BenchmarkConvertToJpeg(b *testing.B) { + options := Options{Type: JPEG} + runBenchmarkResize("test.png", options, 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, + } + 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, + Left: 50, + AreaWidth: 600, + AreaHeight: 480, + } + 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) +} + +func BenchmarkWatermarkImageJpeg(b *testing.B) { + watermark := readFile("transparent.png") + options := Options{ + WatermarkImage: WatermarkImage{ + Buf: watermark, + Opacity: 0.25, + Left: 100, + Top: 100, + }, + } + runBenchmarkResize("test.jpg", options, b) +} + +func BenchmarkWatermarImagePng(b *testing.B) { + watermark := readFile("transparent.png") + options := Options{ + WatermarkImage: WatermarkImage{ + Buf: watermark, + Opacity: 0.25, + Left: 100, + Top: 100, + }, + } + runBenchmarkResize("test.png", options, b) +} + +func BenchmarkWatermarImageWebp(b *testing.B) { + watermark := readFile("transparent.png") + options := Options{ + WatermarkImage: WatermarkImage{ + Buf: watermark, + Opacity: 0.25, + Left: 100, + Top: 100, + }, + } + runBenchmarkResize("test.webp", options, b) +} diff --git a/vendor/src/gopkg.in/h2non/bimg.v1/type.go b/vendor/src/gopkg.in/h2non/bimg.v1/type.go new file mode 100644 index 000000000..260adbf81 --- /dev/null +++ b/vendor/src/gopkg.in/h2non/bimg.v1/type.go @@ -0,0 +1,172 @@ +package bimg + +import ( + "regexp" + "sync" + "unicode/utf8" +) + +const ( + // UNKNOWN represents an unknow image type value. + UNKNOWN ImageType = iota + // JPEG represents the JPEG image type. + JPEG + // WEBP represents the WEBP image type. + WEBP + // PNG represents the PNG image type. + PNG + // TIFF represents the TIFF image type. + TIFF + // GIF represents the GIF image type. + GIF + // PDF represents the PDF type. + PDF + // SVG represents the SVG image type. + SVG + // MAGICK represents the libmagick compatible genetic image type. + MAGICK +) + +// ImageType represents an image type value. +type ImageType int + +var ( + htmlCommentRegex = regexp.MustCompile("(?i)") + svgRegex = regexp.MustCompile(`(?i)^\s*(?:<\?xml[^>]*>\s*)?(?:]*>\s*)?]*>[^*]*<\/svg>\s*$`) +) + +// ImageTypes stores as pairs of image types supported and its alias names. +var ImageTypes = map[ImageType]string{ + JPEG: "jpeg", + PNG: "png", + WEBP: "webp", + TIFF: "tiff", + GIF: "gif", + PDF: "pdf", + SVG: "svg", + MAGICK: "magick", +} + +// imageMutex is used to provide thread-safe synchronization +// for SupportedImageTypes map. +var imageMutex = &sync.RWMutex{} + +// SupportedImageType represents whether a type can be loaded and/or saved by +// the current libvips compilation. +type SupportedImageType struct { + Load bool + Save bool +} + +// SupportedImageTypes stores the optional image type supported +// by the current libvips compilation. +// Note: lazy evaluation as demand is required due +// to bootstrap runtime limitation with C/libvips world. +var SupportedImageTypes = map[ImageType]SupportedImageType{} + +// discoverSupportedImageTypes is used to fill SupportedImageTypes map. +func discoverSupportedImageTypes() { + imageMutex.Lock() + for imageType := range ImageTypes { + SupportedImageTypes[imageType] = SupportedImageType{ + Load: VipsIsTypeSupported(imageType), + Save: VipsIsTypeSupportedSave(imageType), + } + } + imageMutex.Unlock() +} + +// isBinary checks if the given buffer is a binary file. +func isBinary(buf []byte) bool { + if len(buf) < 24 { + return false + } + for i := 0; i < 24; i++ { + charCode, _ := utf8.DecodeRuneInString(string(buf[i])) + if charCode == 65533 || charCode <= 8 { + return true + } + } + return false +} + +// IsSVGImage returns true if the given buffer is a valid SVG image. +func IsSVGImage(buf []byte) bool { + return !isBinary(buf) && svgRegex.Match(htmlCommentRegex.ReplaceAll(buf, []byte{})) +} + +// DetermineImageType determines the image type format (jpeg, png, webp or tiff) +func DetermineImageType(buf []byte) ImageType { + return vipsImageType(buf) +} + +// DetermineImageTypeName determines the image type format by name (jpeg, png, webp or tiff) +func DetermineImageTypeName(buf []byte) string { + return ImageTypeName(vipsImageType(buf)) +} + +// IsImageTypeSupportedByVips returns true if the given image type +// is supported by current libvips compilation. +func IsImageTypeSupportedByVips(t ImageType) SupportedImageType { + imageMutex.RLock() + + // Discover supported image types and cache the result + itShouldDiscover := len(SupportedImageTypes) == 0 + if itShouldDiscover { + imageMutex.RUnlock() + discoverSupportedImageTypes() + } + + // Check if image type is actually supported + supported, ok := SupportedImageTypes[t] + if !itShouldDiscover { + imageMutex.RUnlock() + } + + if ok { + return supported + } + return SupportedImageType{Load: false, Save: false} +} + +// IsTypeSupported checks if a given image type is supported +func IsTypeSupported(t ImageType) bool { + _, ok := ImageTypes[t] + return ok && IsImageTypeSupportedByVips(t).Load +} + +// IsTypeNameSupported checks if a given image type name is supported +func IsTypeNameSupported(t string) bool { + for imageType, name := range ImageTypes { + if name == t { + return IsImageTypeSupportedByVips(imageType).Load + } + } + return false +} + +// IsTypeSupportedSave checks if a given image type is support for saving +func IsTypeSupportedSave(t ImageType) bool { + _, ok := ImageTypes[t] + return ok && IsImageTypeSupportedByVips(t).Save +} + +// IsTypeNameSupportedSave checks if a given image type name is supported for +// saving +func IsTypeNameSupportedSave(t string) bool { + for imageType, name := range ImageTypes { + if name == t { + return IsImageTypeSupportedByVips(imageType).Save + } + } + return false +} + +// ImageTypeName is used to get the human friendly name of an image format. +func ImageTypeName(t ImageType) string { + imageType := ImageTypes[t] + if imageType == "" { + return "unknown" + } + return imageType +} diff --git a/vendor/src/gopkg.in/h2non/bimg.v1/type_test.go b/vendor/src/gopkg.in/h2non/bimg.v1/type_test.go new file mode 100644 index 000000000..9c83b0f52 --- /dev/null +++ b/vendor/src/gopkg.in/h2non/bimg.v1/type_test.go @@ -0,0 +1,128 @@ +package bimg + +import ( + "io/ioutil" + "os" + "path" + "testing" +) + +func TestDeterminateImageType(t *testing.T) { + files := []struct { + name string + expected ImageType + }{ + {"test.jpg", JPEG}, + {"test.png", PNG}, + {"test.webp", WEBP}, + {"test.gif", GIF}, + {"test.pdf", PDF}, + {"test.svg", SVG}, + {"test.jp2", MAGICK}, + } + + for _, file := range files { + img, _ := os.Open(path.Join("fixtures", file.name)) + buf, _ := ioutil.ReadAll(img) + defer img.Close() + + if DetermineImageType(buf) != file.expected { + t.Fatal("Image type is not valid") + } + } +} + +func TestDeterminateImageTypeName(t *testing.T) { + files := []struct { + name string + expected string + }{ + {"test.jpg", "jpeg"}, + {"test.png", "png"}, + {"test.webp", "webp"}, + {"test.gif", "gif"}, + {"test.pdf", "pdf"}, + {"test.svg", "svg"}, + {"test.jp2", "magick"}, + } + + for _, file := range files { + img, _ := os.Open(path.Join("fixtures", file.name)) + buf, _ := ioutil.ReadAll(img) + defer img.Close() + + if DetermineImageTypeName(buf) != file.expected { + t.Fatal("Image type is not valid") + } + } +} + +func TestIsTypeSupported(t *testing.T) { + types := []struct { + name ImageType + }{ + {JPEG}, {PNG}, {WEBP}, {GIF}, {PDF}, + } + + for _, n := range types { + if IsTypeSupported(n.name) == false { + t.Fatalf("Image type %#v is not valid", ImageTypes[n.name]) + } + } +} + +func TestIsTypeNameSupported(t *testing.T) { + types := []struct { + name string + expected bool + }{ + {"jpeg", true}, + {"png", true}, + {"webp", true}, + {"gif", true}, + {"pdf", true}, + } + + for _, n := range types { + if IsTypeNameSupported(n.name) != n.expected { + t.Fatalf("Image type %#v is not valid", n.name) + } + } +} + +func TestIsTypeSupportedSave(t *testing.T) { + types := []struct { + name ImageType + }{ + {JPEG}, {PNG}, {WEBP}, + } + if VipsVersion >= "8.5.0" { + types = append(types, struct{ name ImageType }{TIFF}) + } + + for _, n := range types { + if IsTypeSupportedSave(n.name) == false { + t.Fatalf("Image type %#v is not valid", ImageTypes[n.name]) + } + } +} + +func TestIsTypeNameSupportedSave(t *testing.T) { + types := []struct { + name string + expected bool + }{ + {"jpeg", true}, + {"png", true}, + {"webp", true}, + {"gif", false}, + {"pdf", false}, + {"tiff", VipsVersion >= "8.5.0"}, + } + + for _, n := range types { + if IsTypeNameSupportedSave(n.name) != n.expected { + t.Fatalf("Image type %#v is not valid", n.name) + } + } +} diff --git a/vendor/src/gopkg.in/h2non/bimg.v1/version.go b/vendor/src/gopkg.in/h2non/bimg.v1/version.go new file mode 100644 index 000000000..682eaaaf3 --- /dev/null +++ b/vendor/src/gopkg.in/h2non/bimg.v1/version.go @@ -0,0 +1,4 @@ +package bimg + +// Version represents the current package semantic version. +const Version = "1.0.9" diff --git a/vendor/src/gopkg.in/h2non/bimg.v1/vips.go b/vendor/src/gopkg.in/h2non/bimg.v1/vips.go new file mode 100644 index 000000000..ae654c5cf --- /dev/null +++ b/vendor/src/gopkg.in/h2non/bimg.v1/vips.go @@ -0,0 +1,632 @@ +package bimg + +/* +#cgo pkg-config: vips +#include "vips.h" +*/ +import "C" + +import ( + "errors" + "fmt" + "math" + "os" + "runtime" + "strings" + "sync" + "unsafe" + + d "github.com/tj/go-debug" +) + +// debug is internally used to +var debug = d.Debug("bimg") + +// VipsVersion exposes the current libvips semantic version +const VipsVersion = string(C.VIPS_VERSION) + +// VipsMajorVersion exposes the current libvips major version number +const VipsMajorVersion = int(C.VIPS_MAJOR_VERSION) + +// VipsMinorVersion exposes the current libvips minor version number +const VipsMinorVersion = int(C.VIPS_MINOR_VERSION) + +const ( + maxCacheMem = 100 * 1024 * 1024 + maxCacheSize = 500 +) + +var ( + m sync.Mutex + initialized bool +) + +// VipsMemoryInfo represents the memory stats provided by libvips. +type VipsMemoryInfo struct { + Memory int64 + MemoryHighwater int64 + Allocations int64 +} + +// vipsSaveOptions represents the internal option used to talk with libvips. +type vipsSaveOptions struct { + Quality int + Compression int + Type ImageType + Interlace bool + NoProfile bool + Interpretation Interpretation +} + +type vipsWatermarkOptions struct { + Width C.int + DPI C.int + Margin C.int + NoReplicate C.int + Opacity C.float + Background [3]C.double +} + +type vipsWatermarkImageOptions struct { + Left C.int + Top C.int + Opacity C.float +} + +type vipsWatermarkTextOptions struct { + Text *C.char + Font *C.char +} + +func init() { + Initialize() +} + +// Initialize is used to explicitly start libvips in thread-safe way. +// Only call this function if you have previously turned off 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() + defer runtime.UnlockOSThread() + + err := C.vips_init(C.CString("bimg")) + if err != 0 { + panic("unable to start vips!") + } + + // 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 +} + +// Shutdown is used to shutdown libvips in a thread-safe way. +// 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 { + C.vips_shutdown() + initialized = false + } +} + +// VipsDebugInfo outputs to stdout libvips collected data. Useful for debugging. +func VipsDebugInfo() { + C.im__print_all() +} + +// VipsMemory gets memory info stats from libvips (cache size, memory allocs...) +func VipsMemory() VipsMemoryInfo { + return VipsMemoryInfo{ + Memory: int64(C.vips_tracked_get_mem()), + MemoryHighwater: int64(C.vips_tracked_get_mem_highwater()), + Allocations: int64(C.vips_tracked_get_allocs()), + } +} + +// VipsIsTypeSupported returns true if the given image type +// is supported by the current libvips compilation. +func VipsIsTypeSupported(t ImageType) bool { + if t == JPEG { + return int(C.vips_type_find_bridge(C.JPEG)) != 0 + } + if t == WEBP { + return int(C.vips_type_find_bridge(C.WEBP)) != 0 + } + if t == PNG { + return int(C.vips_type_find_bridge(C.PNG)) != 0 + } + if t == GIF { + return int(C.vips_type_find_bridge(C.GIF)) != 0 + } + if t == PDF { + return int(C.vips_type_find_bridge(C.PDF)) != 0 + } + if t == SVG { + return int(C.vips_type_find_bridge(C.SVG)) != 0 + } + if t == TIFF { + return int(C.vips_type_find_bridge(C.TIFF)) != 0 + } + if t == MAGICK { + return int(C.vips_type_find_bridge(C.MAGICK)) != 0 + } + return false +} + +// VipsIsTypeSupportedSave returns true if the given image type +// is supported by the current libvips compilation for the +// save operation. +func VipsIsTypeSupportedSave(t ImageType) bool { + if t == JPEG { + return int(C.vips_type_find_save_bridge(C.JPEG)) != 0 + } + if t == WEBP { + return int(C.vips_type_find_save_bridge(C.WEBP)) != 0 + } + if t == PNG { + return int(C.vips_type_find_save_bridge(C.PNG)) != 0 + } + if t == TIFF { + return int(C.vips_type_find_save_bridge(C.TIFF)) != 0 + } + return false +} + +func vipsExifOrientation(image *C.VipsImage) int { + return int(C.vips_exif_orientation(image)) +} + +func vipsHasAlpha(image *C.VipsImage) bool { + return int(C.has_alpha_channel(image)) > 0 +} + +func vipsHasProfile(image *C.VipsImage) bool { + return int(C.has_profile_embed(image)) > 0 +} + +func vipsWindowSize(name string) float64 { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + return float64(C.interpolator_window_size(cname)) +} + +func vipsSpace(image *C.VipsImage) string { + return C.GoString(C.vips_enum_nick_bridge(image)) +} + +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)) + if err != 0 { + return nil, catchVipsError() + } + + return out, nil +} + +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)) + if err != 0 { + return nil, catchVipsError() + } + + return out, nil +} + +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)) + if err != 0 { + return nil, catchVipsError() + } + + return out, nil +} + +func vipsWatermark(image *C.VipsImage, w Watermark) (*C.VipsImage, error) { + var out *C.VipsImage + + // Defaults + noReplicate := 0 + if w.NoReplicate { + noReplicate = 1 + } + + text := C.CString(w.Text) + font := C.CString(w.Font) + background := [3]C.double{C.double(w.Background.R), C.double(w.Background.G), C.double(w.Background.B)} + + textOpts := vipsWatermarkTextOptions{text, font} + opts := vipsWatermarkOptions{C.int(w.Width), C.int(w.DPI), C.int(w.Margin), C.int(noReplicate), C.float(w.Opacity), background} + + 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))) + if err != 0 { + return nil, catchVipsError() + } + + return out, nil +} + +func vipsRead(buf []byte) (*C.VipsImage, ImageType, error) { + var image *C.VipsImage + imageType := vipsImageType(buf) + + if imageType == UNKNOWN { + return nil, UNKNOWN, errors.New("Unsupported image format") + } + + length := C.size_t(len(buf)) + imageBuf := unsafe.Pointer(&buf[0]) + + err := C.vips_init_image(imageBuf, length, C.int(imageType), &image) + if err != 0 { + return nil, UNKNOWN, catchVipsError() + } + + return image, imageType, nil +} + +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 InterpretationError, 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), + } + + if vipsHasAlpha(image) { + err := C.vips_flatten_background_brigde(image, &outImage, + backgroundC[0], backgroundC[1], backgroundC[2]) + 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 = InterpretationSRGB + } + 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)) + + tmpImage, err := vipsPreSave(image, &o) + if err != nil { + return nil, err + } + + // When an image has an unsupported color space, vipsPreSave + // returns the pointer of the image passed to it unmodified. + // When this occurs, we must take care to not dereference the + // original image a second time; we may otherwise erroneously + // free the object twice. + if tmpImage != image { + 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) + + if o.Type != 0 && !IsTypeSupportedSave(o.Type) { + return nil, fmt.Errorf("VIPS cannot save to %#v", ImageTypes[o.Type]) + } + var ptr unsafe.Pointer + switch o.Type { + case WEBP: + saveErr = C.vips_webpsave_bridge(tmpImage, &ptr, &length, 1, quality) + case PNG: + saveErr = C.vips_pngsave_bridge(tmpImage, &ptr, &length, 1, C.int(o.Compression), quality, interlace) + case TIFF: + saveErr = C.vips_tiffsave_bridge(tmpImage, &ptr, &length) + default: + saveErr = C.vips_jpegsave_bridge(tmpImage, &ptr, &length, 1, quality, interlace) + } + + if int(saveErr) != 0 { + return nil, catchVipsError() + } + + buf := C.GoBytes(ptr, C.int(length)) + + // Clean up + C.g_free(C.gpointer(ptr)) + C.vips_error_clear() + + return buf, nil +} + +func getImageBuffer(image *C.VipsImage) ([]byte, error) { + var ptr unsafe.Pointer + + length := C.size_t(0) + interlace := C.int(0) + quality := C.int(100) + + err := C.int(0) + err = C.vips_jpegsave_bridge(image, &ptr, &length, 1, quality, interlace) + if int(err) != 0 { + return nil, catchVipsError() + } + + defer C.g_free(C.gpointer(ptr)) + defer C.vips_error_clear() + + return C.GoBytes(ptr, C.int(length)), nil +} + +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 > MaxSize || height > MaxSize { + 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() + } + + return buf, nil +} + +func vipsSmartCrop(image *C.VipsImage, width, height int) (*C.VipsImage, error) { + var buf *C.VipsImage + defer C.g_object_unref(C.gpointer(image)) + + if width > MaxSize || height > MaxSize { + return nil, errors.New("Maximum image size exceeded") + } + + err := C.vips_smartcrop_bridge(image, &buf, C.int(width), C.int(height)) + if err != 0 { + return nil, catchVipsError() + } + + return buf, nil +} + +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(ptr, C.size_t(len(buf)), &image, C.int(shrink)) + if err != 0 { + return nil, catchVipsError() + } + + return image, nil +} + +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))) + if err != 0 { + return nil, catchVipsError() + } + + return image, nil +} + +func vipsEmbed(input *C.VipsImage, left, top, width, height int, extend Extend, background Color) (*C.VipsImage, error) { + var image *C.VipsImage + + // Max extend value, see: http://www.vips.ecs.soton.ac.uk/supported/8.4/doc/html/libvips/libvips-conversion.html#VipsExtend + if extend > 5 { + extend = ExtendBackground + } + + 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), C.double(background.R), C.double(background.G), C.double(background.B)) + if err != 0 { + return nil, catchVipsError() + } + + return image, nil +} + +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(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(residualx), 0, 0, C.double(residualy), interpolator) + if err != 0 { + return nil, catchVipsError() + } + + return image, nil +} + +func vipsImageType(buf []byte) ImageType { + if len(buf) == 0 { + return UNKNOWN + } + if buf[0] == 0x89 && buf[1] == 0x50 && buf[2] == 0x4E && buf[3] == 0x47 { + return PNG + } + if buf[0] == 0xFF && buf[1] == 0xD8 && buf[2] == 0xFF { + return JPEG + } + if IsTypeSupported(WEBP) && buf[8] == 0x57 && buf[9] == 0x45 && buf[10] == 0x42 && buf[11] == 0x50 { + return WEBP + } + if IsTypeSupported(TIFF) && + ((buf[0] == 0x49 && buf[1] == 0x49 && buf[2] == 0x2A && buf[3] == 0x0) || + (buf[0] == 0x4D && buf[1] == 0x4D && buf[2] == 0x0 && buf[3] == 0x2A)) { + return TIFF + } + if IsTypeSupported(GIF) && buf[0] == 0x47 && buf[1] == 0x49 && buf[2] == 0x46 { + return GIF + } + if IsTypeSupported(PDF) && buf[0] == 0x25 && buf[1] == 0x50 && buf[2] == 0x44 && buf[3] == 0x46 { + return PDF + } + if IsTypeSupported(SVG) && IsSVGImage(buf) { + return SVG + } + if IsTypeSupported(MAGICK) && strings.HasSuffix(readImageType(buf), "MagickBuffer") { + return MAGICK + } + return UNKNOWN +} + +func readImageType(buf []byte) string { + length := C.size_t(len(buf)) + imageBuf := unsafe.Pointer(&buf[0]) + load := C.vips_foreign_find_load_buffer(imageBuf, length) + return C.GoString(load) +} + +func catchVipsError() error { + s := C.GoString(C.vips_error_buffer()) + C.vips_error_clear() + 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 +} + +func max(x int) int { + return int(math.Max(float64(x), 0)) +} + +func vipsDrawWatermark(image *C.VipsImage, o WatermarkImage) (*C.VipsImage, error) { + var out *C.VipsImage + + watermark, _, e := vipsRead(o.Buf) + if e != nil { + return nil, e + } + + opts := vipsWatermarkImageOptions{C.int(o.Left), C.int(o.Top), C.float(o.Opacity)} + + err := C.vips_watermark_image(image, watermark, &out, (*C.WatermarkImageOptions)(unsafe.Pointer(&opts))) + + if err != 0 { + return nil, catchVipsError() + } + + return out, nil +} diff --git a/vendor/src/gopkg.in/h2non/bimg.v1/vips.h b/vendor/src/gopkg.in/h2non/bimg.v1/vips.h new file mode 100644 index 000000000..6d7655024 --- /dev/null +++ b/vendor/src/gopkg.in/h2non/bimg.v1/vips.h @@ -0,0 +1,532 @@ +#include +#include +#include +#include +#include + +/** + * 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, + WEBP, + PNG, + TIFF, + GIF, + PDF, + SVG, + MAGICK +}; + +typedef struct { + const char *Text; + const char *Font; +} WatermarkTextOptions; + +typedef struct { + int Width; + int DPI; + int Margin; + int NoReplicate; + float Opacity; + double Background[3]; +} WatermarkOptions; + +typedef struct { + int Left; + int Top; + float Opacity; +} WatermarkImageOptions; + +static unsigned long +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) { + 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) { + return vips_jpegload_buffer(buf, len, out, "shrink", shrink, NULL); +} + +int +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) { + return vips_shrink(in, out, xshrink, yshrink, NULL); +} + +int +vips_type_find_bridge(int t) { + if (t == GIF) { + return vips_type_find("VipsOperation", "gifload"); + } + if (t == PDF) { + return vips_type_find("VipsOperation", "pdfload"); + } + if (t == TIFF) { + return vips_type_find("VipsOperation", "tiffload"); + } + if (t == SVG) { + return vips_type_find("VipsOperation", "svgload"); + } + if (t == WEBP) { + return vips_type_find("VipsOperation", "webpload"); + } + if (t == PNG) { + return vips_type_find("VipsOperation", "pngload"); + } + if (t == JPEG) { + return vips_type_find("VipsOperation", "jpegload"); + } + if (t == MAGICK) { + return vips_type_find("VipsOperation", "magickload"); + } + return 0; +} + +int +vips_type_find_save_bridge(int t) { + if (t == TIFF) { + return vips_type_find("VipsOperation", "tiffsave_buffer"); + } + if (t == WEBP) { + return vips_type_find("VipsOperation", "webpsave_buffer"); + } + if (t == PNG) { + return vips_type_find("VipsOperation", "pngsave_buffer"); + } + if (t == JPEG) { + return vips_type_find("VipsOperation", "jpegsave_buffer"); + } + return 0; +} + +int +vips_rotate(VipsImage *in, VipsImage **out, int angle) { + int rotate = VIPS_ANGLE_D0; + + angle %= 360; + + if (angle == 45) { + rotate = VIPS_ANGLE45_D45; + } else if (angle == 90) { + rotate = VIPS_ANGLE_D90; + } else if (angle == 135) { + rotate = VIPS_ANGLE45_D135; + } else if (angle == 180) { + rotate = VIPS_ANGLE_D180; + } else if (angle == 225) { + rotate = VIPS_ANGLE45_D225; + } else if (angle == 270) { + rotate = VIPS_ANGLE_D270; + } else if (angle == 315) { + rotate = VIPS_ANGLE45_D315; + } else { + angle = 0; + } + + if (angle > 0 && angle % 90 != 0) { + return vips_rot45(in, out, "angle", rotate, NULL); + } else { + return vips_rot(in, out, rotate, NULL); + } +} + +int +vips_exif_orientation(VipsImage *image) { + int orientation = 0; + const char *exif; + if ( + vips_image_get_typeof(image, EXIF_IFD0_ORIENTATION) != 0 && + !vips_image_get_string(image, EXIF_IFD0_ORIENTATION, &exif) + ) { + orientation = atoi(&exif[0]); + } + return orientation; +} + +int +interpolator_window_size(char const *name) { + VipsInterpolate *interpolator = vips_interpolate_new(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) { + return vips_zoom(in, out, xfac, yfac, NULL); +} + +int +vips_embed_bridge(VipsImage *in, VipsImage **out, int left, int top, int width, int height, int extend, double r, double g, double b) { + if (extend == VIPS_EXTEND_BACKGROUND) { + double background[3] = {r, g, b}; + VipsArrayDouble *vipsBackground = vips_array_double_new(background, 3); + return vips_embed(in, out, left, top, width, height, "extend", extend, "background", vipsBackground, NULL); + } + 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) { + return vips_extract_area(in, out, left, top, width, height, NULL); +} + +int +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_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", with_interlace(interlace), + "filter", VIPS_FOREIGN_PNG_FILTER_NONE, + NULL + ); +#else + 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) { + return vips_webpsave_buffer(in, buf, len, + "strip", strip, + "Q", quality, + NULL + ); +} + +int +vips_tiffsave_bridge(VipsImage *in, void **buf, size_t *len) { +#if (VIPS_MAJOR_VERSION >= 8 && VIPS_MINOR_VERSION >= 5) + return vips_tiffsave_buffer(in, buf, len, NULL); +#else + return 0; +#endif +} + +int +vips_is_16bit (VipsInterpretation interpretation) { + return interpretation == VIPS_INTERPRETATION_RGB16 || interpretation == VIPS_INTERPRETATION_GREY16; +} + +int +vips_flatten_background_brigde(VipsImage *in, VipsImage **out, double r, double g, double b) { + if (vips_is_16bit(in->Type)) { + r = 65535 * r / 255; + g = 65535 * g / 255; + b = 65535 * b / 255; + } + + double background[3] = {r, g, b}; + VipsArrayDouble *vipsBackground = vips_array_double_new(background, 3); + + return vips_flatten(in, out, + "background", vipsBackground, + "max_alpha", vips_is_16bit(in->Type) ? 65535.0 : 255.0, + 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_RANDOM, NULL); + } else if (imageType == PNG) { + 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_RANDOM, NULL); + } else if (imageType == TIFF) { + code = vips_tiffload_buffer(buf, len, out, "access", VIPS_ACCESS_RANDOM, NULL); +#if (VIPS_MAJOR_VERSION >= 8) +#if (VIPS_MINOR_VERSION >= 3) + } else if (imageType == GIF) { + code = vips_gifload_buffer(buf, len, out, "access", VIPS_ACCESS_RANDOM, NULL); + } else if (imageType == PDF) { + code = vips_pdfload_buffer(buf, len, out, "access", VIPS_ACCESS_RANDOM, NULL); + } else if (imageType == SVG) { + code = vips_svgload_buffer(buf, len, out, "access", VIPS_ACCESS_RANDOM, NULL); +#endif + } else if (imageType == MAGICK) { + code = vips_magickload_buffer(buf, len, out, "access", VIPS_ACCESS_RANDOM, NULL); +#endif + } + + return code; +} + +int +vips_watermark_replicate (VipsImage *orig, VipsImage *in, VipsImage **out) { + VipsImage *cache = vips_image_new(); + + 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; + } + + g_object_unref(cache); + return 0; +} + +int +vips_watermark(VipsImage *in, VipsImage **out, WatermarkTextOptions *to, WatermarkOptions *o) { + double ones[3] = { 1, 1, 1 }; + + VipsImage *base = vips_image_new(); + VipsImage **t = (VipsImage **) vips_object_local_array(VIPS_OBJECT(base), 10); + t[0] = in; + + // 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; + } + + // 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 +} + +int +vips_add_band(VipsImage *in, VipsImage **out, double c) { +#if (VIPS_MAJOR_VERSION > 8 || (VIPS_MAJOR_VERSION >= 8 && VIPS_MINOR_VERSION >= 2)) + return vips_bandjoin_const1(in, out, c, NULL); +#else + VipsImage *base = vips_image_new(); + if ( + vips_black(&base, in->Xsize, in->Ysize, NULL) || + vips_linear1(base, &base, 1, c, NULL)) { + g_object_unref(base); + return 1; + } + g_object_unref(base); + return vips_bandjoin2(in, base, out, c, NULL); +#endif +} + +int +vips_watermark_image(VipsImage *in, VipsImage *sub, VipsImage **out, WatermarkImageOptions *o) { + VipsImage *base = vips_image_new(); + VipsImage **t = (VipsImage **) vips_object_local_array(VIPS_OBJECT(base), 10); + + // add in and sub for unreffing and later use + t[0] = in; + t[1] = sub; + + if (has_alpha_channel(in) == 0) { + vips_add_band(in, &t[0], 255.0); + // in is no longer in the array and won't be unreffed, so add it at the end + t[8] = in; + } + + if (has_alpha_channel(sub) == 0) { + vips_add_band(sub, &t[1], 255.0); + // sub is no longer in the array and won't be unreffed, so add it at the end + t[9] = sub; + } + + // Place watermark image in the right place and size it to the size of the + // image that should be watermarked + if ( + vips_embed(t[1], &t[2], o->Left, o->Top, t[0]->Xsize, t[0]->Ysize, NULL)) { + g_object_unref(base); + return 1; + } + + // Create a mask image based on the alpha band from the watermark image + // and place it in the right position + if ( + vips_extract_band(t[1], &t[3], t[1]->Bands - 1, "n", 1, NULL) || + vips_linear1(t[3], &t[4], o->Opacity, 0.0, NULL) || + vips_cast(t[4], &t[5], VIPS_FORMAT_UCHAR, NULL) || + vips_copy(t[5], &t[6], "interpretation", t[0]->Type, NULL) || + vips_embed(t[6], &t[7], o->Left, o->Top, t[0]->Xsize, t[0]->Ysize, NULL)) { + g_object_unref(base); + return 1; + } + + // Blend the mask and watermark image and write to output. + if (vips_ifthenelse(t[7], t[2], t[0], out, "blend", TRUE, NULL)) { + g_object_unref(base); + return 1; + } + + g_object_unref(base); + return 0; +} + +int +vips_smartcrop_bridge(VipsImage *in, VipsImage **out, int width, int height) { +#if (VIPS_MAJOR_VERSION >= 8 && VIPS_MINOR_VERSION >= 5) + return vips_smartcrop(in, out, width, height, NULL); +#else + return 0; +#endif +} diff --git a/vendor/src/gopkg.in/h2non/bimg.v1/vips_test.go b/vendor/src/gopkg.in/h2non/bimg.v1/vips_test.go new file mode 100644 index 000000000..47b3f4140 --- /dev/null +++ b/vendor/src/gopkg.in/h2non/bimg.v1/vips_test.go @@ -0,0 +1,163 @@ +package bimg + +import ( + "io/ioutil" + "os" + "path" + "testing" +) + +func TestVipsRead(t *testing.T) { + files := []struct { + name string + expected ImageType + }{ + {"test.jpg", JPEG}, + {"test.png", PNG}, + {"test.webp", WEBP}, + } + + for _, file := range files { + image, imageType, _ := vipsRead(readImage(file.name)) + if image == nil { + t.Fatal("Empty image") + } + if imageType != file.expected { + t.Fatal("Invalid image type") + } + } +} + +func TestVipsSave(t *testing.T) { + types := [...]ImageType{JPEG, PNG, WEBP} + + for _, typ := range types { + image, _, _ := vipsRead(readImage("test.jpg")) + options := vipsSaveOptions{Quality: 95, Type: typ} + + buf, err := vipsSave(image, options) + if err != nil { + t.Fatalf("Cannot save the image as '%v'", ImageTypes[typ]) + } + if len(buf) == 0 { + t.Fatalf("Empty saved '%v' image", ImageTypes[typ]) + } + } +} + +func TestVipsSaveTiff(t *testing.T) { + if !IsTypeSupportedSave(TIFF) { + t.Skipf("Format %#v is not supported", ImageTypes[TIFF]) + } + image, _, _ := vipsRead(readImage("test.jpg")) + options := vipsSaveOptions{Quality: 95, Type: TIFF} + buf, _ := vipsSave(image, options) + + if len(buf) == 0 { + t.Fatalf("Empty saved '%v' image", ImageTypes[TIFF]) + } +} + +func TestVipsRotate(t *testing.T) { + files := []struct { + name string + rotate Angle + }{ + {"test.jpg", D90}, + {"test_square.jpg", D45}, + } + + for _, file := range files { + image, _, _ := vipsRead(readImage(file.name)) + + newImg, err := vipsRotate(image, file.rotate) + if err != nil { + t.Fatal("Cannot rotate 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 TestVipsWatermarkWithImage(t *testing.T) { + image, _, _ := vipsRead(readImage("test.jpg")) + + watermark := readImage("transparent.png") + + options := WatermarkImage{Left: 100, Top: 100, Opacity: 1.0, Buf: watermark} + newImg, err := vipsDrawWatermark(image, options) + 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 +}