aboutsummaryrefslogtreecommitdiff
path: root/vendor/github.com/disintegration
diff options
context:
space:
mode:
authorTulir Asokan <tulir@maunium.net>2018-04-22 21:25:06 +0300
committerTulir Asokan <tulir@maunium.net>2018-04-22 21:25:51 +0300
commit64fa922ec013079f8f0c90fc9e93c56db3611d30 (patch)
tree7bb9b40f57b8368ef0f5eeccea02d80e54796927 /vendor/github.com/disintegration
parentbfb5f0dd457be326b1ae7638a64d2e79cbace371 (diff)
Switch to dep
Diffstat (limited to 'vendor/github.com/disintegration')
-rw-r--r--vendor/github.com/disintegration/imaging/.travis.yml13
-rw-r--r--vendor/github.com/disintegration/imaging/LICENSE21
-rw-r--r--vendor/github.com/disintegration/imaging/README.md188
-rw-r--r--vendor/github.com/disintegration/imaging/adjust.go222
-rw-r--r--vendor/github.com/disintegration/imaging/convolution.go146
-rw-r--r--vendor/github.com/disintegration/imaging/doc.go7
-rw-r--r--vendor/github.com/disintegration/imaging/effects.go165
-rw-r--r--vendor/github.com/disintegration/imaging/helpers.go280
-rw-r--r--vendor/github.com/disintegration/imaging/histogram.go51
-rw-r--r--vendor/github.com/disintegration/imaging/resize.go572
-rw-r--r--vendor/github.com/disintegration/imaging/scanner.go250
-rw-r--r--vendor/github.com/disintegration/imaging/tools.go213
-rw-r--r--vendor/github.com/disintegration/imaging/transform.go271
-rw-r--r--vendor/github.com/disintegration/imaging/utils.go83
14 files changed, 2482 insertions, 0 deletions
diff --git a/vendor/github.com/disintegration/imaging/.travis.yml b/vendor/github.com/disintegration/imaging/.travis.yml
new file mode 100644
index 0000000..89370ed
--- /dev/null
+++ b/vendor/github.com/disintegration/imaging/.travis.yml
@@ -0,0 +1,13 @@
+language: go
+go:
+ - "1.7.x"
+ - "1.8.x"
+ - "1.9.x"
+ - "1.10.x"
+
+before_install:
+ - go get github.com/mattn/goveralls
+
+script:
+ - go test -v -race -cover
+ - $GOPATH/bin/goveralls -service=travis-ci
diff --git a/vendor/github.com/disintegration/imaging/LICENSE b/vendor/github.com/disintegration/imaging/LICENSE
new file mode 100644
index 0000000..c68f7ab
--- /dev/null
+++ b/vendor/github.com/disintegration/imaging/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2012-2018 Grigory Dryapak
+
+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/github.com/disintegration/imaging/README.md b/vendor/github.com/disintegration/imaging/README.md
new file mode 100644
index 0000000..c7ee30f
--- /dev/null
+++ b/vendor/github.com/disintegration/imaging/README.md
@@ -0,0 +1,188 @@
+# Imaging
+
+[![GoDoc](https://godoc.org/github.com/disintegration/imaging?status.svg)](https://godoc.org/github.com/disintegration/imaging)
+[![Build Status](https://travis-ci.org/disintegration/imaging.svg?branch=master)](https://travis-ci.org/disintegration/imaging)
+[![Coverage Status](https://coveralls.io/repos/github/disintegration/imaging/badge.svg?branch=master&service=github)](https://coveralls.io/github/disintegration/imaging?branch=master)
+[![Go Report Card](https://goreportcard.com/badge/github.com/disintegration/imaging)](https://goreportcard.com/report/github.com/disintegration/imaging)
+
+Package imaging provides basic image processing functions (resize, rotate, crop, brightness/contrast adjustments, etc.).
+
+All the image processing functions provided by the package accept any image type that implements `image.Image` interface
+as an input, and return a new image of `*image.NRGBA` type (32bit RGBA colors, not premultiplied by alpha).
+
+## Installation
+
+ go get -u github.com/disintegration/imaging
+
+## Documentation
+
+http://godoc.org/github.com/disintegration/imaging
+
+## Usage examples
+
+A few usage examples can be found below. See the documentation for the full list of supported functions.
+
+### Image resizing
+
+```go
+// Resize srcImage to size = 128x128px using the Lanczos filter.
+dstImage128 := imaging.Resize(srcImage, 128, 128, imaging.Lanczos)
+
+// Resize srcImage to width = 800px preserving the aspect ratio.
+dstImage800 := imaging.Resize(srcImage, 800, 0, imaging.Lanczos)
+
+// Scale down srcImage to fit the 800x600px bounding box.
+dstImageFit := imaging.Fit(srcImage, 800, 600, imaging.Lanczos)
+
+// Resize and crop the srcImage to fill the 100x100px area.
+dstImageFill := imaging.Fill(srcImage, 100, 100, imaging.Center, imaging.Lanczos)
+```
+
+Imaging supports image resizing using various resampling filters. The most notable ones:
+- `NearestNeighbor` - Fastest resampling filter, no antialiasing.
+- `Box` - Simple and fast averaging filter appropriate for downscaling. When upscaling it's similar to NearestNeighbor.
+- `Linear` - Bilinear filter, smooth and reasonably fast.
+- `MitchellNetravali` - А smooth bicubic filter.
+- `CatmullRom` - A sharp bicubic filter.
+- `Gaussian` - Blurring filter that uses gaussian function, useful for noise removal.
+- `Lanczos` - High-quality resampling filter for photographic images yielding sharp results, slower than cubic filters.
+
+The full list of supported filters: NearestNeighbor, Box, Linear, Hermite, MitchellNetravali, CatmullRom, BSpline, Gaussian, Lanczos, Hann, Hamming, Blackman, Bartlett, Welch, Cosine. Custom filters can be created using ResampleFilter struct.
+
+**Resampling filters comparison**
+
+Original image:
+
+![srcImage](testdata/branches.png)
+
+The same image resized from 600x400px to 150x100px using different resampling filters.
+From faster (lower quality) to slower (higher quality):
+
+Filter | Resize result
+--------------------------|---------------------------------------------
+`imaging.NearestNeighbor` | ![dstImage](testdata/out_resize_nearest.png)
+`imaging.Linear` | ![dstImage](testdata/out_resize_linear.png)
+`imaging.CatmullRom` | ![dstImage](testdata/out_resize_catrom.png)
+`imaging.Lanczos` | ![dstImage](testdata/out_resize_lanczos.png)
+
+
+### Gaussian Blur
+
+```go
+dstImage := imaging.Blur(srcImage, 0.5)
+```
+
+Sigma parameter allows to control the strength of the blurring effect.
+
+Original image | Sigma = 0.5 | Sigma = 1.5
+-----------------------------------|----------------------------------------|---------------------------------------
+![srcImage](testdata/flowers_small.png) | ![dstImage](testdata/out_blur_0.5.png) | ![dstImage](testdata/out_blur_1.5.png)
+
+### Sharpening
+
+```go
+dstImage := imaging.Sharpen(srcImage, 0.5)
+```
+
+`Sharpen` uses gaussian function internally. Sigma parameter allows to control the strength of the sharpening effect.
+
+Original image | Sigma = 0.5 | Sigma = 1.5
+-----------------------------------|-------------------------------------------|------------------------------------------
+![srcImage](testdata/flowers_small.png) | ![dstImage](testdata/out_sharpen_0.5.png) | ![dstImage](testdata/out_sharpen_1.5.png)
+
+### Gamma correction
+
+```go
+dstImage := imaging.AdjustGamma(srcImage, 0.75)
+```
+
+Original image | Gamma = 0.75 | Gamma = 1.25
+-----------------------------------|------------------------------------------|-----------------------------------------
+![srcImage](testdata/flowers_small.png) | ![dstImage](testdata/out_gamma_0.75.png) | ![dstImage](testdata/out_gamma_1.25.png)
+
+### Contrast adjustment
+
+```go
+dstImage := imaging.AdjustContrast(srcImage, 20)
+```
+
+Original image | Contrast = 15 | Contrast = -15
+-----------------------------------|--------------------------------------------|-------------------------------------------
+![srcImage](testdata/flowers_small.png) | ![dstImage](testdata/out_contrast_p15.png) | ![dstImage](testdata/out_contrast_m15.png)
+
+### Brightness adjustment
+
+```go
+dstImage := imaging.AdjustBrightness(srcImage, 20)
+```
+
+Original image | Brightness = 10 | Brightness = -10
+-----------------------------------|----------------------------------------------|---------------------------------------------
+![srcImage](testdata/flowers_small.png) | ![dstImage](testdata/out_brightness_p10.png) | ![dstImage](testdata/out_brightness_m10.png)
+
+## Example code
+
+```go
+package main
+
+import (
+ "image"
+ "image/color"
+ "log"
+
+ "github.com/disintegration/imaging"
+)
+
+func main() {
+ // Open a test image.
+ src, err := imaging.Open("testdata/flowers.png")
+ if err != nil {
+ log.Fatalf("failed to open image: %v", err)
+ }
+
+ // Crop the original image to 300x300px size using the center anchor.
+ src = imaging.CropAnchor(src, 300, 300, imaging.Center)
+
+ // Resize the cropped image to width = 200px preserving the aspect ratio.
+ src = imaging.Resize(src, 200, 0, imaging.Lanczos)
+
+ // Create a blurred version of the image.
+ img1 := imaging.Blur(src, 5)
+
+ // Create a grayscale version of the image with higher contrast and sharpness.
+ img2 := imaging.Grayscale(src)
+ img2 = imaging.AdjustContrast(img2, 20)
+ img2 = imaging.Sharpen(img2, 2)
+
+ // Create an inverted version of the image.
+ img3 := imaging.Invert(src)
+
+ // Create an embossed version of the image using a convolution filter.
+ img4 := imaging.Convolve3x3(
+ src,
+ [9]float64{
+ -1, -1, 0,
+ -1, 1, 1,
+ 0, 1, 1,
+ },
+ nil,
+ )
+
+ // Create a new image and paste the four produced images into it.
+ dst := imaging.New(400, 400, color.NRGBA{0, 0, 0, 0})
+ dst = imaging.Paste(dst, img1, image.Pt(0, 0))
+ dst = imaging.Paste(dst, img2, image.Pt(0, 200))
+ dst = imaging.Paste(dst, img3, image.Pt(200, 0))
+ dst = imaging.Paste(dst, img4, image.Pt(200, 200))
+
+ // Save the resulting image as JPEG.
+ err = imaging.Save(dst, "testdata/out_example.jpg")
+ if err != nil {
+ log.Fatalf("failed to save image: %v", err)
+ }
+}
+```
+
+Output:
+
+![dstImage](testdata/out_example.jpg) \ No newline at end of file
diff --git a/vendor/github.com/disintegration/imaging/adjust.go b/vendor/github.com/disintegration/imaging/adjust.go
new file mode 100644
index 0000000..fb3a9ce
--- /dev/null
+++ b/vendor/github.com/disintegration/imaging/adjust.go
@@ -0,0 +1,222 @@
+package imaging
+
+import (
+ "image"
+ "image/color"
+ "math"
+)
+
+// Grayscale produces a grayscale version of the image.
+func Grayscale(img image.Image) *image.NRGBA {
+ src := newScanner(img)
+ dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h))
+ parallel(0, src.h, func(ys <-chan int) {
+ for y := range ys {
+ i := y * dst.Stride
+ src.scan(0, y, src.w, y+1, dst.Pix[i:i+src.w*4])
+ for x := 0; x < src.w; x++ {
+ r := dst.Pix[i+0]
+ g := dst.Pix[i+1]
+ b := dst.Pix[i+2]
+ f := 0.299*float64(r) + 0.587*float64(g) + 0.114*float64(b)
+ y := uint8(f + 0.5)
+ dst.Pix[i+0] = y
+ dst.Pix[i+1] = y
+ dst.Pix[i+2] = y
+ i += 4
+ }
+ }
+ })
+ return dst
+}
+
+// Invert produces an inverted (negated) version of the image.
+func Invert(img image.Image) *image.NRGBA {
+ src := newScanner(img)
+ dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h))
+ parallel(0, src.h, func(ys <-chan int) {
+ for y := range ys {
+ i := y * dst.Stride
+ src.scan(0, y, src.w, y+1, dst.Pix[i:i+src.w*4])
+ for x := 0; x < src.w; x++ {
+ dst.Pix[i+0] = 255 - dst.Pix[i+0]
+ dst.Pix[i+1] = 255 - dst.Pix[i+1]
+ dst.Pix[i+2] = 255 - dst.Pix[i+2]
+ i += 4
+ }
+ }
+ })
+ return dst
+}
+
+// AdjustContrast changes the contrast of the image using the percentage parameter and returns the adjusted image.
+// The percentage must be in range (-100, 100). The percentage = 0 gives the original image.
+// The percentage = -100 gives solid gray image.
+//
+// Examples:
+//
+// dstImage = imaging.AdjustContrast(srcImage, -10) // decrease image contrast by 10%
+// dstImage = imaging.AdjustContrast(srcImage, 20) // increase image contrast by 20%
+//
+func AdjustContrast(img image.Image, percentage float64) *image.NRGBA {
+ percentage = math.Min(math.Max(percentage, -100.0), 100.0)
+ lut := make([]uint8, 256)
+
+ v := (100.0 + percentage) / 100.0
+ for i := 0; i < 256; i++ {
+ if 0 <= v && v <= 1 {
+ lut[i] = clamp((0.5 + (float64(i)/255.0-0.5)*v) * 255.0)
+ } else if 1 < v && v < 2 {
+ lut[i] = clamp((0.5 + (float64(i)/255.0-0.5)*(1/(2.0-v))) * 255.0)
+ } else {
+ lut[i] = uint8(float64(i)/255.0+0.5) * 255
+ }
+ }
+
+ return adjustLUT(img, lut)
+}
+
+// AdjustBrightness changes the brightness of the image using the percentage parameter and returns the adjusted image.
+// The percentage must be in range (-100, 100). The percentage = 0 gives the original image.
+// The percentage = -100 gives solid black image. The percentage = 100 gives solid white image.
+//
+// Examples:
+//
+// dstImage = imaging.AdjustBrightness(srcImage, -15) // decrease image brightness by 15%
+// dstImage = imaging.AdjustBrightness(srcImage, 10) // increase image brightness by 10%
+//
+func AdjustBrightness(img image.Image, percentage float64) *image.NRGBA {
+ percentage = math.Min(math.Max(percentage, -100.0), 100.0)
+ lut := make([]uint8, 256)
+
+ shift := 255.0 * percentage / 100.0
+ for i := 0; i < 256; i++ {
+ lut[i] = clamp(float64(i) + shift)
+ }
+
+ return adjustLUT(img, lut)
+}
+
+// AdjustGamma performs a gamma correction on the image and returns the adjusted image.
+// Gamma parameter must be positive. Gamma = 1.0 gives the original image.
+// Gamma less than 1.0 darkens the image and gamma greater than 1.0 lightens it.
+//
+// Example:
+//
+// dstImage = imaging.AdjustGamma(srcImage, 0.7)
+//
+func AdjustGamma(img image.Image, gamma float64) *image.NRGBA {
+ e := 1.0 / math.Max(gamma, 0.0001)
+ lut := make([]uint8, 256)
+
+ for i := 0; i < 256; i++ {
+ lut[i] = clamp(math.Pow(float64(i)/255.0, e) * 255.0)
+ }
+
+ return adjustLUT(img, lut)
+}
+
+// AdjustSigmoid changes the contrast of the image using a sigmoidal function and returns the adjusted image.
+// It's a non-linear contrast change useful for photo adjustments as it preserves highlight and shadow detail.
+// The midpoint parameter is the midpoint of contrast that must be between 0 and 1, typically 0.5.
+// The factor parameter indicates how much to increase or decrease the contrast, typically in range (-10, 10).
+// If the factor parameter is positive the image contrast is increased otherwise the contrast is decreased.
+//
+// Examples:
+//
+// dstImage = imaging.AdjustSigmoid(srcImage, 0.5, 3.0) // increase the contrast
+// dstImage = imaging.AdjustSigmoid(srcImage, 0.5, -3.0) // decrease the contrast
+//
+func AdjustSigmoid(img image.Image, midpoint, factor float64) *image.NRGBA {
+ if factor == 0 {
+ return Clone(img)
+ }
+
+ lut := make([]uint8, 256)
+ a := math.Min(math.Max(midpoint, 0.0), 1.0)
+ b := math.Abs(factor)
+ sig0 := sigmoid(a, b, 0)
+ sig1 := sigmoid(a, b, 1)
+ e := 1.0e-6
+
+ if factor > 0 {
+ for i := 0; i < 256; i++ {
+ x := float64(i) / 255.0
+ sigX := sigmoid(a, b, x)
+ f := (sigX - sig0) / (sig1 - sig0)
+ lut[i] = clamp(f * 255.0)
+ }
+ } else {
+ for i := 0; i < 256; i++ {
+ x := float64(i) / 255.0
+ arg := math.Min(math.Max((sig1-sig0)*x+sig0, e), 1.0-e)
+ f := a - math.Log(1.0/arg-1.0)/b
+ lut[i] = clamp(f * 255.0)
+ }
+ }
+
+ return adjustLUT(img, lut)
+}
+
+func sigmoid(a, b, x float64) float64 {
+ return 1 / (1 + math.Exp(b*(a-x)))
+}
+
+// adjustLUT applies the given lookup table to the colors of the image.
+func adjustLUT(img image.Image, lut []uint8) *image.NRGBA {
+ src := newScanner(img)
+ dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h))
+ parallel(0, src.h, func(ys <-chan int) {
+ for y := range ys {
+ i := y * dst.Stride
+ src.scan(0, y, src.w, y+1, dst.Pix[i:i+src.w*4])
+ for x := 0; x < src.w; x++ {
+ dst.Pix[i+0] = lut[dst.Pix[i+0]]
+ dst.Pix[i+1] = lut[dst.Pix[i+1]]
+ dst.Pix[i+2] = lut[dst.Pix[i+2]]
+ i += 4
+ }
+ }
+ })
+ return dst
+}
+
+// AdjustFunc applies the fn function to each pixel of the img image and returns the adjusted image.
+//
+// Example:
+//
+// dstImage = imaging.AdjustFunc(
+// srcImage,
+// func(c color.NRGBA) color.NRGBA {
+// // shift the red channel by 16
+// r := int(c.R) + 16
+// if r > 255 {
+// r = 255
+// }
+// return color.NRGBA{uint8(r), c.G, c.B, c.A}
+// }
+// )
+//
+func AdjustFunc(img image.Image, fn func(c color.NRGBA) color.NRGBA) *image.NRGBA {
+ src := newScanner(img)
+ dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h))
+ parallel(0, src.h, func(ys <-chan int) {
+ for y := range ys {
+ i := y * dst.Stride
+ src.scan(0, y, src.w, y+1, dst.Pix[i:i+src.w*4])
+ for x := 0; x < src.w; x++ {
+ r := dst.Pix[i+0]
+ g := dst.Pix[i+1]
+ b := dst.Pix[i+2]
+ a := dst.Pix[i+3]
+ c := fn(color.NRGBA{r, g, b, a})
+ dst.Pix[i+0] = c.R
+ dst.Pix[i+1] = c.G
+ dst.Pix[i+2] = c.B
+ dst.Pix[i+3] = c.A
+ i += 4
+ }
+ }
+ })
+ return dst
+}
diff --git a/vendor/github.com/disintegration/imaging/convolution.go b/vendor/github.com/disintegration/imaging/convolution.go
new file mode 100644
index 0000000..9e6404d
--- /dev/null
+++ b/vendor/github.com/disintegration/imaging/convolution.go
@@ -0,0 +1,146 @@
+package imaging
+
+import (
+ "image"
+)
+
+// ConvolveOptions are convolution parameters.
+type ConvolveOptions struct {
+ // If Normalize is true the kernel is normalized before convolution.
+ Normalize bool
+
+ // If Abs is true the absolute value of each color channel is taken after convolution.
+ Abs bool
+
+ // Bias is added to each color channel value after convolution.
+ Bias int
+}
+
+// Convolve3x3 convolves the image with the specified 3x3 convolution kernel.
+// Default parameters are used if a nil *ConvolveOptions is passed.
+func Convolve3x3(img image.Image, kernel [9]float64, options *ConvolveOptions) *image.NRGBA {
+ return convolve(img, kernel[:], options)
+}
+
+// Convolve5x5 convolves the image with the specified 5x5 convolution kernel.
+// Default parameters are used if a nil *ConvolveOptions is passed.
+func Convolve5x5(img image.Image, kernel [25]float64, options *ConvolveOptions) *image.NRGBA {
+ return convolve(img, kernel[:], options)
+}
+
+func convolve(img image.Image, kernel []float64, options *ConvolveOptions) *image.NRGBA {
+ src := toNRGBA(img)
+ w := src.Bounds().Max.X
+ h := src.Bounds().Max.Y
+ dst := image.NewNRGBA(image.Rect(0, 0, w, h))
+
+ if w < 1 || h < 1 {
+ return dst
+ }
+
+ if options == nil {
+ options = &ConvolveOptions{}
+ }
+
+ if options.Normalize {
+ normalizeKernel(kernel)
+ }
+
+ type coef struct {
+ x, y int
+ k float64
+ }
+ var coefs []coef
+ var m int
+
+ switch len(kernel) {
+ case 9:
+ m = 1
+ case 25:
+ m = 2
+ }
+
+ i := 0
+ for y := -m; y <= m; y++ {
+ for x := -m; x <= m; x++ {
+ if kernel[i] != 0 {
+ coefs = append(coefs, coef{x: x, y: y, k: kernel[i]})
+ }
+ i++
+ }
+ }
+
+ parallel(0, h, func(ys <-chan int) {
+ for y := range ys {
+ for x := 0; x < w; x++ {
+ var r, g, b float64
+ for _, c := range coefs {
+ ix := x + c.x
+ if ix < 0 {
+ ix = 0
+ } else if ix >= w {
+ ix = w - 1
+ }
+
+ iy := y + c.y
+ if iy < 0 {
+ iy = 0
+ } else if iy >= h {
+ iy = h - 1
+ }
+
+ off := iy*src.Stride + ix*4
+ r += float64(src.Pix[off+0]) * c.k
+ g += float64(src.Pix[off+1]) * c.k
+ b += float64(src.Pix[off+2]) * c.k
+ }
+
+ if options.Abs {
+ if r < 0 {
+ r = -r
+ }
+ if g < 0 {
+ g = -g
+ }
+ if b < 0 {
+ b = -b
+ }
+ }
+
+ if options.Bias != 0 {
+ r += float64(options.Bias)
+ g += float64(options.Bias)
+ b += float64(options.Bias)
+ }
+
+ srcOff := y*src.Stride + x*4
+ dstOff := y*dst.Stride + x*4
+ dst.Pix[dstOff+0] = clamp(r)
+ dst.Pix[dstOff+1] = clamp(g)
+ dst.Pix[dstOff+2] = clamp(b)
+ dst.Pix[dstOff+3] = src.Pix[srcOff+3]
+ }
+ }
+ })
+
+ return dst
+}
+
+func normalizeKernel(kernel []float64) {
+ var sum, sumpos float64
+ for i := range kernel {
+ sum += kernel[i]
+ if kernel[i] > 0 {
+ sumpos += kernel[i]
+ }
+ }
+ if sum != 0 {
+ for i := range kernel {
+ kernel[i] /= sum
+ }
+ } else if sumpos != 0 {
+ for i := range kernel {
+ kernel[i] /= sumpos
+ }
+ }
+}
diff --git a/vendor/github.com/disintegration/imaging/doc.go b/vendor/github.com/disintegration/imaging/doc.go
new file mode 100644
index 0000000..5d59b46
--- /dev/null
+++ b/vendor/github.com/disintegration/imaging/doc.go
@@ -0,0 +1,7 @@
+/*
+Package imaging provides basic image processing functions (resize, rotate, crop, brightness/contrast adjustments, etc.).
+
+All the image processing functions provided by the package accept any image type that implements image.Image interface
+as an input, and return a new image of *image.NRGBA type (32bit RGBA colors, not premultiplied by alpha).
+*/
+package imaging
diff --git a/vendor/github.com/disintegration/imaging/effects.go b/vendor/github.com/disintegration/imaging/effects.go
new file mode 100644
index 0000000..b16781f
--- /dev/null
+++ b/vendor/github.com/disintegration/imaging/effects.go
@@ -0,0 +1,165 @@
+package imaging
+
+import (
+ "image"
+ "math"
+)
+
+func gaussianBlurKernel(x, sigma float64) float64 {
+ return math.Exp(-(x*x)/(2*sigma*sigma)) / (sigma * math.Sqrt(2*math.Pi))
+}
+
+// Blur produces a blurred version of the image using a Gaussian function.
+// Sigma parameter must be positive and indicates how much the image will be blurred.
+//
+// Usage example:
+//
+// dstImage := imaging.Blur(srcImage, 3.5)
+//
+func Blur(img image.Image, sigma float64) *image.NRGBA {
+ if sigma <= 0 {
+ return Clone(img)
+ }
+
+ radius := int(math.Ceil(sigma * 3.0))
+ kernel := make([]float64, radius+1)
+
+ for i := 0; i <= radius; i++ {
+ kernel[i] = gaussianBlurKernel(float64(i), sigma)
+ }
+
+ return blurVertical(blurHorizontal(img, kernel), kernel)
+}
+
+func blurHorizontal(img image.Image, kernel []float64) *image.NRGBA {
+ src := newScanner(img)
+ dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h))
+ radius := len(kernel) - 1
+
+ parallel(0, src.h, func(ys <-chan int) {
+ scanLine := make([]uint8, src.w*4)
+ for y := range ys {
+ src.scan(0, y, src.w, y+1, scanLine)
+ for x := 0; x < src.w; x++ {
+ min := x - radius
+ if min < 0 {
+ min = 0
+ }
+ max := x + radius
+ if max > src.w-1 {
+ max = src.w - 1
+ }
+
+ var r, g, b, a, wsum float64
+ for ix := min; ix <= max; ix++ {
+ i := ix * 4
+ weight := kernel[absint(x-ix)]
+ wsum += weight
+ wa := float64(scanLine[i+3]) * weight
+ r += float64(scanLine[i+0]) * wa
+ g += float64(scanLine[i+1]) * wa
+ b += float64(scanLine[i+2]) * wa
+ a += wa
+ }
+ if a != 0 {
+ r /= a
+ g /= a
+ b /= a
+ }
+
+ j := y*dst.Stride + x*4
+ dst.Pix[j+0] = clamp(r)
+ dst.Pix[j+1] = clamp(g)
+ dst.Pix[j+2] = clamp(b)
+ dst.Pix[j+3] = clamp(a / wsum)
+ }
+ }
+ })
+
+ return dst
+}
+
+func blurVertical(img image.Image, kernel []float64) *image.NRGBA {
+ src := newScanner(img)
+ dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h))
+ radius := len(kernel) - 1
+
+ parallel(0, src.w, func(xs <-chan int) {
+ scanLine := make([]uint8, src.h*4)
+ for x := range xs {
+ src.scan(x, 0, x+1, src.h, scanLine)
+ for y := 0; y < src.h; y++ {
+ min := y - radius
+ if min < 0 {
+ min = 0
+ }
+ max := y + radius
+ if max > src.h-1 {
+ max = src.h - 1
+ }
+
+ var r, g, b, a, wsum float64
+ for iy := min; iy <= max; iy++ {
+ i := iy * 4
+ weight := kernel[absint(y-iy)]
+ wsum += weight
+ wa := float64(scanLine[i+3]) * weight
+ r += float64(scanLine[i+0]) * wa
+ g += float64(scanLine[i+1]) * wa
+ b += float64(scanLine[i+2]) * wa
+ a += wa
+ }
+ if a != 0 {
+ r /= a
+ g /= a
+ b /= a
+ }
+
+ j := y*dst.Stride + x*4
+ dst.Pix[j+0] = clamp(r)
+ dst.Pix[j+1] = clamp(g)
+ dst.Pix[j+2] = clamp(b)
+ dst.Pix[j+3] = clamp(a / wsum)
+ }
+ }
+ })
+
+ return dst
+}
+
+// Sharpen produces a sharpened version of the image.
+// Sigma parameter must be positive and indicates how much the image will be sharpened.
+//
+// Usage example:
+//
+// dstImage := imaging.Sharpen(srcImage, 3.5)
+//
+func Sharpen(img image.Image, sigma float64) *image.NRGBA {
+ if sigma <= 0 {
+ return Clone(img)
+ }
+
+ src := newScanner(img)
+ dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h))
+ blurred := Blur(img, sigma)
+
+ parallel(0, src.h, func(ys <-chan int) {
+ scanLine := make([]uint8, src.w*4)
+ for y := range ys {
+ src.scan(0, y, src.w, y+1, scanLine)
+ j := y * dst.Stride
+ for i := 0; i < src.w*4; i++ {
+ val := int(scanLine[i])<<1 - int(blurred.Pix[j])
+ if val < 0 {
+ val = 0
+ } else if val > 0xff {
+ val = 0xff
+ }
+ dst.Pix[j] = uint8(val)
+ j++
+ }
+ }
+ })
+
+ return dst
+}
diff --git a/vendor/github.com/disintegration/imaging/helpers.go b/vendor/github.com/disintegration/imaging/helpers.go
new file mode 100644
index 0000000..7193e47
--- /dev/null
+++ b/vendor/github.com/disintegration/imaging/helpers.go
@@ -0,0 +1,280 @@
+package imaging
+
+import (
+ "errors"
+ "image"
+ "image/color"
+ "image/draw"
+ "image/gif"
+ "image/jpeg"
+ "image/png"
+ "io"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "golang.org/x/image/bmp"
+ "golang.org/x/image/tiff"
+)
+
+// Format is an image file format.
+type Format int
+
+// Image file formats.
+const (
+ JPEG Format = iota
+ PNG
+ GIF
+ TIFF
+ BMP
+)
+
+func (f Format) String() string {
+ switch f {
+ case JPEG:
+ return "JPEG"
+ case PNG:
+ return "PNG"
+ case GIF:
+ return "GIF"
+ case TIFF:
+ return "TIFF"
+ case BMP:
+ return "BMP"
+ default:
+ return "Unsupported"
+ }
+}
+
+var (
+ // ErrUnsupportedFormat means the given image format (or file extension) is unsupported.
+ ErrUnsupportedFormat = errors.New("imaging: unsupported image format")
+)
+
+type fileSystem interface {
+ Create(string) (io.WriteCloser, error)
+ Open(string) (io.ReadCloser, error)
+}
+
+type localFS struct{}
+
+func (localFS) Create(name string) (io.WriteCloser, error) { return os.Create(name) }
+func (localFS) Open(name string) (io.ReadCloser, error) { return os.Open(name) }
+
+var fs fileSystem = localFS{}
+
+// Decode reads an image from r.
+func Decode(r io.Reader) (image.Image, error) {
+ img, _, err := image.Decode(r)
+ return img, err
+}
+
+// Open loads an image from file
+func Open(filename string) (image.Image, error) {
+ file, err := fs.Open(filename)
+ if err != nil {
+ return nil, err
+ }
+ defer file.Close()
+ return Decode(file)
+}
+
+type encodeConfig struct {
+ jpegQuality int
+ gifNumColors int
+ gifQuantizer draw.Quantizer
+ gifDrawer draw.Drawer
+ pngCompressionLevel png.CompressionLevel
+}
+
+var defaultEncodeConfig = encodeConfig{
+ jpegQuality: 95,
+ gifNumColors: 256,
+ gifQuantizer: nil,
+ gifDrawer: nil,
+ pngCompressionLevel: png.DefaultCompression,
+}
+
+// EncodeOption sets an optional parameter for the Encode and Save functions.
+type EncodeOption func(*encodeConfig)
+
+// JPEGQuality returns an EncodeOption that sets the output JPEG quality.
+// Quality ranges from 1 to 100 inclusive, higher is better. Default is 95.
+func JPEGQuality(quality int) EncodeOption {
+ return func(c *encodeConfig) {
+ c.jpegQuality = quality
+ }
+}
+
+// GIFNumColors returns an EncodeOption that sets the maximum number of colors
+// used in the GIF-encoded image. It ranges from 1 to 256. Default is 256.
+func GIFNumColors(numColors int) EncodeOption {
+ return func(c *encodeConfig) {
+ c.gifNumColors = numColors
+ }
+}
+
+// GIFQuantizer returns an EncodeOption that sets the quantizer that is used to produce
+// a palette of the GIF-encoded image.
+func GIFQuantizer(quantizer draw.Quantizer) EncodeOption {
+ return func(c *encodeConfig) {
+ c.gifQuantizer = quantizer
+ }
+}
+
+// GIFDrawer returns an EncodeOption that sets the drawer that is used to convert
+// the source image to the desired palette of the GIF-encoded image.
+func GIFDrawer(drawer draw.Drawer) EncodeOption {
+ return func(c *encodeConfig) {
+ c.gifDrawer = drawer
+ }
+}
+
+// PNGCompressionLevel returns an EncodeOption that sets the compression level
+// of the PNG-encoded image. Default is png.DefaultCompression.
+func PNGCompressionLevel(level png.CompressionLevel) EncodeOption {
+ return func(c *encodeConfig) {
+ c.pngCompressionLevel = level
+ }
+}
+
+// Encode writes the image img to w in the specified format (JPEG, PNG, GIF, TIFF or BMP).
+func Encode(w io.Writer, img image.Image, format Format, opts ...EncodeOption) error {
+ cfg := defaultEncodeConfig
+ for _, option := range opts {
+ option(&cfg)
+ }
+
+ var err error
+ switch format {
+ case JPEG:
+ var rgba *image.RGBA
+ if nrgba, ok := img.(*image.NRGBA); ok {
+ if nrgba.Opaque() {
+ rgba = &image.RGBA{
+ Pix: nrgba.Pix,
+ Stride: nrgba.Stride,
+ Rect: nrgba.Rect,
+ }
+ }
+ }
+ if rgba != nil {
+ err = jpeg.Encode(w, rgba, &jpeg.Options{Quality: cfg.jpegQuality})
+ } else {
+ err = jpeg.Encode(w, img, &jpeg.Options{Quality: cfg.jpegQuality})
+ }
+
+ case PNG:
+ enc := png.Encoder{CompressionLevel: cfg.pngCompressionLevel}
+ err = enc.Encode(w, img)
+
+ case GIF:
+ err = gif.Encode(w, img, &gif.Options{
+ NumColors: cfg.gifNumColors,
+ Quantizer: cfg.gifQuantizer,
+ Drawer: cfg.gifDrawer,
+ })
+
+ case TIFF:
+ err = tiff.Encode(w, img, &tiff.Options{Compression: tiff.Deflate, Predictor: true})
+
+ case BMP:
+ err = bmp.Encode(w, img)
+
+ default:
+ err = ErrUnsupportedFormat
+ }
+ return err
+}
+
+// Save saves the image to file with the specified filename.
+// The format is determined from the filename extension: "jpg" (or "jpeg"), "png", "gif", "tif" (or "tiff") and "bmp" are supported.
+//
+// Examples:
+//
+// // Save the image as PNG.
+// err := imaging.Save(img, "out.png")
+//
+// // Save the image as JPEG with optional quality parameter set to 80.
+// err := imaging.Save(img, "out.jpg", imaging.JPEGQuality(80))
+//
+func Save(img image.Image, filename string, opts ...EncodeOption) (err error) {
+ formats := map[string]Format{
+ ".jpg": JPEG,
+ ".jpeg": JPEG,
+ ".png": PNG,
+ ".tif": TIFF,
+ ".tiff": TIFF,
+ ".bmp": BMP,
+ ".gif": GIF,
+ }
+
+ ext := strings.ToLower(filepath.Ext(filename))
+ f, ok := formats[ext]
+ if !ok {
+ return ErrUnsupportedFormat
+ }
+
+ file, err := fs.Create(filename)
+ if err != nil {
+ return err
+ }
+
+ defer func() {
+ cerr := file.Close()
+ if err == nil {
+ err = cerr
+ }
+ }()
+
+ return Encode(file, img, f, opts...)
+}
+
+// New creates a new image with the specified width and height, and fills it with the specified color.
+func New(width, height int, fillColor color.Color) *image.NRGBA {
+ if width <= 0 || height <= 0 {
+ return &image.NRGBA{}
+ }
+
+ dst := image.NewNRGBA(image.Rect(0, 0, width, height))
+ c := color.NRGBAModel.Convert(fillColor).(color.NRGBA)
+
+ if c.R == 0 && c.G == 0 && c.B == 0 && c.A == 0 {
+ return dst
+ }
+
+ // Fill the first row.
+ i := 0
+ for x := 0; x < width; x++ {
+ dst.Pix[i+0] = c.R
+ dst.Pix[i+1] = c.G
+ dst.Pix[i+2] = c.B
+ dst.Pix[i+3] = c.A
+ i += 4
+ }
+
+ // Copy the first row to other rows.
+ size := width * 4
+ parallel(1, height, func(ys <-chan int) {
+ for y := range ys {
+ i = y * dst.Stride
+ copy(dst.Pix[i:i+size], dst.Pix[0:size])
+ }
+ })
+
+ return dst
+}
+
+// Clone returns a copy of the given image.
+func Clone(img image.Image) *image.NRGBA {
+ src := newScanner(img)
+ dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h))
+ size := src.w * 4
+ parallel(0, src.h, func(ys <-chan int) {
+ for y := range ys {
+ i := y * dst.Stride
+ src.scan(0, y, src.w, y+1, dst.Pix[i:i+size])
+ }
+ })
+ return dst
+}
diff --git a/vendor/github.com/disintegration/imaging/histogram.go b/vendor/github.com/disintegration/imaging/histogram.go
new file mode 100644
index 0000000..5bcb001
--- /dev/null
+++ b/vendor/github.com/disintegration/imaging/histogram.go
@@ -0,0 +1,51 @@
+package imaging
+
+import (
+ "image"
+ "sync"
+)
+
+// Histogram returns a normalized histogram of an image.
+//
+// Resulting histogram is represented as an array of 256 floats, where
+// histogram[i] is a probability of a pixel being of a particular luminance i.
+func Histogram(img image.Image) [256]float64 {
+ var mu sync.Mutex
+ var histogram [256]float64
+ var total float64
+
+ src := newScanner(img)
+ if src.w == 0 || src.h == 0 {
+ return histogram
+ }
+
+ parallel(0, src.h, func(ys <-chan int) {
+ var tmpHistogram [256]float64
+ var tmpTotal float64
+ scanLine := make([]uint8, src.w*4)
+ for y := range ys {
+ src.scan(0, y, src.w, y+1, scanLine)
+ i := 0
+ for x := 0; x < src.w; x++ {
+ r := scanLine[i+0]
+ g := scanLine[i+1]
+ b := scanLine[i+2]
+ y := 0.299*float32(r) + 0.587*float32(g) + 0.114*float32(b)
+ tmpHistogram[int(y+0.5)]++
+ tmpTotal++
+ i += 4
+ }
+ }
+ mu.Lock()
+ for i := 0; i < 256; i++ {
+ histogram[i] += tmpHistogram[i]
+ }
+ total += tmpTotal
+ mu.Unlock()
+ })
+
+ for i := 0; i < 256; i++ {
+ histogram[i] = histogram[i] / total
+ }
+ return histogram
+}
diff --git a/vendor/github.com/disintegration/imaging/resize.go b/vendor/github.com/disintegration/imaging/resize.go
new file mode 100644
index 0000000..97f498a
--- /dev/null
+++ b/vendor/github.com/disintegration/imaging/resize.go
@@ -0,0 +1,572 @@
+package imaging
+
+import (
+ "image"
+ "math"
+)
+
+type indexWeight struct {
+ index int
+ weight float64
+}
+
+func precomputeWeights(dstSize, srcSize int, filter ResampleFilter) [][]indexWeight {
+ du := float64(srcSize) / float64(dstSize)
+ scale := du
+ if scale < 1.0 {
+ scale = 1.0
+ }
+ ru := math.Ceil(scale * filter.Support)
+
+ out := make([][]indexWeight, dstSize)
+ tmp := make([]indexWeight, 0, dstSize*int(ru+2)*2)
+
+ for v := 0; v < dstSize; v++ {
+ fu := (float64(v)+0.5)*du - 0.5
+
+ begin := int(math.Ceil(fu - ru))
+ if begin < 0 {
+ begin = 0
+ }
+ end := int(math.Floor(fu + ru))
+ if end > srcSize-1 {
+ end = srcSize - 1
+ }
+
+ var sum float64
+ for u := begin; u <= end; u++ {
+ w := filter.Kernel((float64(u) - fu) / scale)
+ if w != 0 {
+ sum += w
+ tmp = append(tmp, indexWeight{index: u, weight: w})
+ }
+ }
+ if sum != 0 {
+ for i := range tmp {
+ tmp[i].weight /= sum
+ }
+ }
+
+ out[v] = tmp
+ tmp = tmp[len(tmp):]
+ }
+
+ return out
+}
+
+// Resize resizes the image to the specified width and height using the specified resampling
+// filter and returns the transformed image. If one of width or height is 0, the image aspect
+// ratio is preserved.
+//
+// Supported resample filters: NearestNeighbor, Box, Linear, Hermite, MitchellNetravali,
+// CatmullRom, BSpline, Gaussian, Lanczos, Hann, Hamming, Blackman, Bartlett, Welch, Cosine.
+//
+// Usage example:
+//
+// dstImage := imaging.Resize(srcImage, 800, 600, imaging.Lanczos)
+//
+func Resize(img image.Image, width, height int, filter ResampleFilter) *image.NRGBA {
+ dstW, dstH := width, height
+ if dstW < 0 || dstH < 0 {
+ return &image.NRGBA{}
+ }
+ if dstW == 0 && dstH == 0 {
+ return &image.NRGBA{}
+ }
+
+ srcW := img.Bounds().Dx()
+ srcH := img.Bounds().Dy()
+ if srcW <= 0 || srcH <= 0 {
+ return &image.NRGBA{}
+ }
+
+ // If new width or height is 0 then preserve aspect ratio, minimum 1px.
+ if dstW == 0 {
+ tmpW := float64(dstH) * float64(srcW) / float64(srcH)
+ dstW = int(math.Max(1.0, math.Floor(tmpW+0.5)))
+ }
+ if dstH == 0 {
+ tmpH := float64(dstW) * float64(srcH) / float64(srcW)
+ dstH = int(math.Max(1.0, math.Floor(tmpH+0.5)))
+ }
+
+ if filter.Support <= 0 {
+ // Nearest-neighbor special case.
+ return resizeNearest(img, dstW, dstH)
+ }
+
+ if srcW != dstW && srcH != dstH {
+ return resizeVertical(resizeHorizontal(img, dstW, filter), dstH, filter)
+ }
+ if srcW != dstW {
+ return resizeHorizontal(img, dstW, filter)
+ }
+ if srcH != dstH {
+ return resizeVertical(img, dstH, filter)
+ }
+ return Clone(img)
+}
+
+func resizeHorizontal(img image.Image, width int, filter ResampleFilter) *image.NRGBA {
+ src := newScanner(img)
+ dst := image.NewNRGBA(image.Rect(0, 0, width, src.h))
+ weights := precomputeWeights(width, src.w, filter)
+ parallel(0, src.h, func(ys <-chan int) {
+ scanLine := make([]uint8, src.w*4)
+ for y := range ys {
+ src.scan(0, y, src.w, y+1, scanLine)
+ j0 := y * dst.Stride
+ for x := 0; x < width; x++ {
+ var r, g, b, a float64
+ for _, w := range weights[x] {
+ i := w.index * 4
+ aw := float64(scanLine[i+3]) * w.weight
+ r += float64(scanLine[i+0]) * aw
+ g += float64(scanLine[i+1]) * aw
+ b += float64(scanLine[i+2]) * aw
+ a += aw
+ }
+ if a != 0 {
+ aInv := 1 / a
+ j := j0 + x*4
+ dst.Pix[j+0] = clamp(r * aInv)
+ dst.Pix[j+1] = clamp(g * aInv)
+ dst.Pix[j+2] = clamp(b * aInv)
+ dst.Pix[j+3] = clamp(a)
+ }
+ }
+ }
+ })
+ return dst
+}
+
+func resizeVertical(img image.Image, height int, filter ResampleFilter) *image.NRGBA {
+ src := newScanner(img)
+ dst := image.NewNRGBA(image.Rect(0, 0, src.w, height))
+ weights := precomputeWeights(height, src.h, filter)
+ parallel(0, src.w, func(xs <-chan int) {
+ scanLine := make([]uint8, src.h*4)
+ for x := range xs {
+ src.scan(x, 0, x+1, src.h, scanLine)
+ for y := 0; y < height; y++ {
+ var r, g, b, a float64
+ for _, w := range weights[y] {
+ i := w.index * 4
+ aw := float64(scanLine[i+3]) * w.weight
+ r += float64(scanLine[i+0]) * aw
+ g += float64(scanLine[i+1]) * aw
+ b += float64(scanLine[i+2]) * aw
+ a += aw
+ }
+ if a != 0 {
+ aInv := 1 / a
+ j := y*dst.Stride + x*4
+ dst.Pix[j+0] = clamp(r * aInv)
+ dst.Pix[j+1] = clamp(g * aInv)
+ dst.Pix[j+2] = clamp(b * aInv)
+ dst.Pix[j+3] = clamp(a)
+ }
+ }
+ }
+ })
+ return dst
+}
+
+// resizeNearest is a fast nearest-neighbor resize, no filtering.
+func resizeNearest(img image.Image, width, height int) *image.NRGBA {
+ dst := image.NewNRGBA(image.Rect(0, 0, width, height))
+ dx := float64(img.Bounds().Dx()) / float64(width)
+ dy := float64(img.Bounds().Dy()) / float64(height)
+
+ if dx > 1 && dy > 1 {
+ src := newScanner(img)
+ parallel(0, height, func(ys <-chan int) {
+ for y := range ys {
+ srcY := int((float64(y) + 0.5) * dy)
+ dstOff := y * dst.Stride
+ for x := 0; x < width; x++ {
+ srcX := int((float64(x) + 0.5) * dx)
+ src.scan(srcX, srcY, srcX+1, srcY+1, dst.Pix[dstOff:dstOff+4])
+ dstOff += 4
+ }
+ }
+ })
+ } else {
+ src := toNRGBA(img)
+ parallel(0, height, func(ys <-chan int) {
+ for y := range ys {
+ srcY := int((float64(y) + 0.5) * dy)
+ srcOff0 := srcY * src.Stride
+ dstOff := y * dst.Stride
+ for x := 0; x < width; x++ {
+ srcX := int((float64(x) + 0.5) * dx)
+ srcOff := srcOff0 + srcX*4
+ copy(dst.Pix[dstOff:dstOff+4], src.Pix[srcOff:srcOff+4])
+ dstOff += 4
+ }
+ }
+ })
+ }
+
+ return dst
+}
+
+// Fit scales down the image using the specified resample filter to fit the specified
+// maximum width and height and returns the transformed image.
+//
+// Supported resample filters: NearestNeighbor, Box, Linear, Hermite, MitchellNetravali,
+// CatmullRom, BSpline, Gaussian, Lanczos, Hann, Hamming, Blackman, Bartlett, Welch, Cosine.
+//
+// Usage example:
+//
+// dstImage := imaging.Fit(srcImage, 800, 600, imaging.Lanczos)
+//
+func Fit(img image.Image, width, height int, filter ResampleFilter) *image.NRGBA {
+ maxW, maxH := width, height
+
+ if maxW <= 0 || maxH <= 0 {
+ return &image.NRGBA{}
+ }
+
+ srcBounds := img.Bounds()
+ srcW := srcBounds.Dx()
+ srcH := srcBounds.Dy()
+
+ if srcW <= 0 || srcH <= 0 {
+ return &image.NRGBA{}
+ }
+
+ if srcW <= maxW && srcH <= maxH {
+ return Clone(img)
+ }
+
+ srcAspectRatio := float64(srcW) / float64(srcH)
+ maxAspectRatio := float64(maxW) / float64(maxH)
+
+ var newW, newH int
+ if srcAspectRatio > maxAspectRatio {
+ newW = maxW
+ newH = int(float64(newW) / srcAspectRatio)
+ } else {
+ newH = maxH
+ newW = int(float64(newH) * srcAspectRatio)
+ }
+
+ return Resize(img, newW, newH, filter)
+}
+
+// Fill scales the image to the smallest possible size that will cover the specified dimensions,
+// crops the resized image to the specified dimensions using the given anchor point and returns
+// the transformed image.
+//
+// Supported resample filters: NearestNeighbor, Box, Linear, Hermite, MitchellNetravali,
+// CatmullRom, BSpline, Gaussian, Lanczos, Hann, Hamming, Blackman, Bartlett, Welch, Cosine.
+//
+// Usage example:
+//
+// dstImage := imaging.Fill(srcImage, 800, 600, imaging.Center, imaging.Lanczos)
+//
+func Fill(img image.Image, width, height int, anchor Anchor, filter ResampleFilter) *image.NRGBA {
+ minW, minH := width, height
+
+ if minW <= 0 || minH <= 0 {
+ return &image.NRGBA{}
+ }
+
+ srcBounds := img.Bounds()
+ srcW := srcBounds.Dx()
+ srcH := srcBounds.Dy()
+
+ if srcW <= 0 || srcH <= 0 {
+ return &image.NRGBA{}
+ }
+
+ if srcW == minW && srcH == minH {
+ return Clone(img)
+ }
+
+ srcAspectRatio := float64(srcW) / float64(srcH)
+ minAspectRatio := float64(minW) / float64(minH)
+
+ var tmp *image.NRGBA
+ if srcAspectRatio < minAspectRatio {
+ tmp = Resize(img, minW, 0, filter)
+ } else {
+ tmp = Resize(img, 0, minH, filter)
+ }
+
+ return CropAnchor(tmp, minW, minH, anchor)
+}
+
+// Thumbnail scales the image up or down using the specified resample filter, crops it
+// to the specified width and hight and returns the transformed image.
+//
+// Supported resample filters: NearestNeighbor, Box, Linear, Hermite, MitchellNetravali,
+// CatmullRom, BSpline, Gaussian, Lanczos, Hann, Hamming, Blackman, Bartlett, Welch, Cosine.
+//
+// Usage example:
+//
+// dstImage := imaging.Thumbnail(srcImage, 100, 100, imaging.Lanczos)
+//
+func Thumbnail(img image.Image, width, height int, filter ResampleFilter) *image.NRGBA {
+ return Fill(img, width, height, Center, filter)
+}
+
+// ResampleFilter is a resampling filter struct. It can be used to define custom filters.
+//
+// Supported resample filters: NearestNeighbor, Box, Linear, Hermite, MitchellNetravali,
+// CatmullRom, BSpline, Gaussian, Lanczos, Hann, Hamming, Blackman, Bartlett, Welch, Cosine.
+//
+// General filter recommendations:
+//
+// - Lanczos
+// High-quality resampling filter for photographic images yielding sharp results.
+// It's slower than cubic filters (see below).
+//
+// - CatmullRom
+// A sharp cubic filter. It's a good filter for both upscaling and downscaling if sharp results are needed.
+//
+// - MitchellNetravali
+// A high quality cubic filter that produces smoother results with less ringing artifacts than CatmullRom.
+//
+// - BSpline
+// A good filter if a very smooth output is needed.
+//
+// - Linear
+// Bilinear interpolation filter, produces reasonably good, smooth output.
+// It's faster than cubic filters.
+//
+// - Box
+// Simple and fast averaging filter appropriate for downscaling.
+// When upscaling it's similar to NearestNeighbor.
+//
+// - NearestNeighbor
+// Fastest resampling filter, no antialiasing.
+//
+type ResampleFilter struct {
+ Support float64
+ Kernel func(float64) float64
+}
+
+// NearestNeighbor is a nearest-neighbor filter (no anti-aliasing).
+var NearestNeighbor ResampleFilter
+
+// Box filter (averaging pixels).
+var Box ResampleFilter
+
+// Linear filter.
+var Linear ResampleFilter
+
+// Hermite cubic spline filter (BC-spline; B=0; C=0).
+var Hermite ResampleFilter
+
+// MitchellNetravali is Mitchell-Netravali cubic filter (BC-spline; B=1/3; C=1/3).
+var MitchellNetravali ResampleFilter
+
+// CatmullRom is a Catmull-Rom - sharp cubic filter (BC-spline; B=0; C=0.5).
+var CatmullRom ResampleFilter
+
+// BSpline is a smooth cubic filter (BC-spline; B=1; C=0).
+var BSpline ResampleFilter
+
+// Gaussian is a Gaussian blurring Filter.
+var Gaussian ResampleFilter
+
+// Bartlett is a Bartlett-windowed sinc filter (3 lobes).
+var Bartlett ResampleFilter
+
+// Lanczos filter (3 lobes).
+var Lanczos ResampleFilter
+
+// Hann is a Hann-windowed sinc filter (3 lobes).
+var Hann ResampleFilter
+
+// Hamming is a Hamming-windowed sinc filter (3 lobes).
+var Hamming ResampleFilter
+
+// Blackman is a Blackman-windowed sinc filter (3 lobes).
+var Blackman ResampleFilter
+
+// Welch is a Welch-windowed sinc filter (parabolic window, 3 lobes).
+var Welch ResampleFilter
+
+// Cosine is a Cosine-windowed sinc filter (3 lobes).
+var Cosine ResampleFilter
+
+func bcspline(x, b, c float64) float64 {
+ var y float64
+ x = math.Abs(x)
+ if x < 1.0 {
+ y = ((12-9*b-6*c)*x*x*x + (-18+12*b+6*c)*x*x + (6 - 2*b)) / 6
+ } else if x < 2.0 {
+ y = ((-b-6*c)*x*x*x + (6*b+30*c)*x*x + (-12*b-48*c)*x + (8*b + 24*c)) / 6
+ }
+ return y
+}
+
+func sinc(x float64) float64 {
+ if x == 0 {
+ return 1
+ }
+ return math.Sin(math.Pi*x) / (math.Pi * x)
+}
+
+func init() {
+ NearestNeighbor = ResampleFilter{
+ Support: 0.0, // special case - not applying the filter
+ }
+
+ Box = ResampleFilter{
+ Support: 0.5,
+ Kernel: func(x float64) float64 {
+ x = math.Abs(x)
+ if x <= 0.5 {
+ return 1.0
+ }
+ return 0
+ },
+ }
+
+ Linear = ResampleFilter{
+ Support: 1.0,
+ Kernel: func(x float64) float64 {
+ x = math.Abs(x)
+ if x < 1.0 {
+ return 1.0 - x
+ }
+ return 0
+ },
+ }
+
+ Hermite = ResampleFilter{
+ Support: 1.0,
+ Kernel: func(x float64) float64 {
+ x = math.Abs(x)
+ if x < 1.0 {
+ return bcspline(x, 0.0, 0.0)
+ }
+ return 0
+ },
+ }
+
+ MitchellNetravali = ResampleFilter{
+ Support: 2.0,
+ Kernel: func(x float64) float64 {
+ x = math.Abs(x)
+ if x < 2.0 {
+ return bcspline(x, 1.0/3.0, 1.0/3.0)
+ }
+ return 0
+ },
+ }
+
+ CatmullRom = ResampleFilter{
+ Support: 2.0,
+ Kernel: func(x float64) float64 {
+ x = math.Abs(x)
+ if x < 2.0 {
+ return bcspline(x, 0.0, 0.5)
+ }
+ return 0
+ },
+ }
+
+ BSpline = ResampleFilter{
+ Support: 2.0,
+ Kernel: func(x float64) float64 {
+ x = math.Abs(x)
+ if x < 2.0 {
+ return bcspline(x, 1.0, 0.0)
+ }
+ return 0
+ },
+ }
+
+ Gaussian = ResampleFilter{
+ Support: 2.0,
+ Kernel: func(x float64) float64 {
+ x = math.Abs(x)
+ if x < 2.0 {
+ return math.Exp(-2 * x * x)
+ }
+ return 0
+ },
+ }
+
+ Bartlett = ResampleFilter{
+ Support: 3.0,
+ Kernel: func(x float64) float64 {
+ x = math.Abs(x)
+ if x < 3.0 {
+ return sinc(x) * (3.0 - x) / 3.0
+ }
+ return 0
+ },
+ }
+
+ Lanczos = ResampleFilter{
+ Support: 3.0,
+ Kernel: func(x float64) float64 {
+ x = math.Abs(x)
+ if x < 3.0 {
+ return sinc(x) * sinc(x/3.0)
+ }
+ return 0
+ },
+ }
+
+ Hann = ResampleFilter{
+ Support: 3.0,
+ Kernel: func(x float64) float64 {
+ x = math.Abs(x)
+ if x < 3.0 {
+ return sinc(x) * (0.5 + 0.5*math.Cos(math.Pi*x/3.0))
+ }
+ return 0
+ },
+ }
+
+ Hamming = ResampleFilter{
+ Support: 3.0,
+ Kernel: func(x float64) float64 {
+ x = math.Abs(x)
+ if x < 3.0 {
+ return sinc(x) * (0.54 + 0.46*math.Cos(math.Pi*x/3.0))
+ }
+ return 0
+ },
+ }
+
+ Blackman = ResampleFilter{
+ Support: 3.0,
+ Kernel: func(x float64) float64 {
+ x = math.Abs(x)
+ if x < 3.0 {
+ return sinc(x) * (0.42 - 0.5*math.Cos(math.Pi*x/3.0+math.Pi) + 0.08*math.Cos(2.0*math.Pi*x/3.0))
+ }
+ return 0
+ },
+ }
+
+ Welch = ResampleFilter{
+ Support: 3.0,
+ Kernel: func(x float64) float64 {
+ x = math.Abs(x)
+ if x < 3.0 {
+ return sinc(x) * (1.0 - (x * x / 9.0))
+ }
+ return 0
+ },
+ }
+
+ Cosine = ResampleFilter{
+ Support: 3.0,
+ Kernel: func(x float64) float64 {
+ x = math.Abs(x)
+ if x < 3.0 {
+ return sinc(x) * math.Cos((math.Pi/2.0)*(x/3.0))
+ }
+ return 0
+ },
+ }
+}
diff --git a/vendor/github.com/disintegration/imaging/scanner.go b/vendor/github.com/disintegration/imaging/scanner.go
new file mode 100644
index 0000000..c4dbfe1
--- /dev/null
+++ b/vendor/github.com/disintegration/imaging/scanner.go
@@ -0,0 +1,250 @@
+package imaging
+
+import (
+ "image"
+ "image/color"
+)
+
+type scanner struct {
+ image image.Image
+ w, h int
+ palette []color.NRGBA
+}
+
+func newScanner(img image.Image) *scanner {
+ s := &scanner{
+ image: img,
+ w: img.Bounds().Dx(),
+ h: img.Bounds().Dy(),
+ }
+ if img, ok := img.(*image.Paletted); ok {
+ s.palette = make([]color.NRGBA, len(img.Palette))
+ for i := 0; i < len(img.Palette); i++ {
+ s.palette[i] = color.NRGBAModel.Convert(img.Palette[i]).(color.NRGBA)
+ }
+ }
+ return s
+}
+
+// scan scans the given rectangular region of the image into dst.
+func (s *scanner) scan(x1, y1, x2, y2 int, dst []uint8) {
+ switch img := s.image.(type) {
+ case *image.NRGBA:
+ size := (x2 - x1) * 4
+ j := 0
+ i := y1*img.Stride + x1*4
+ for y := y1; y < y2; y++ {
+ copy(dst[j:j+size], img.Pix[i:i+size])
+ j += size
+ i += img.Stride
+ }
+
+ case *image.NRGBA64:
+ j := 0
+ for y := y1; y < y2; y++ {
+ i := y*img.Stride + x1*8
+ for x := x1; x < x2; x++ {
+ dst[j+0] = img.Pix[i+0]
+ dst[j+1] = img.Pix[i+2]
+ dst[j+2] = img.Pix[i+4]
+ dst[j+3] = img.Pix[i+6]
+ j += 4
+ i += 8
+ }
+ }
+
+ case *image.RGBA:
+ j := 0
+ for y := y1; y < y2; y++ {
+ i := y*img.Stride + x1*4
+ for x := x1; x < x2; x++ {
+ a := img.Pix[i+3]
+ switch a {
+ case 0:
+ dst[j+0] = 0
+ dst[j+1] = 0
+ dst[j+2] = 0
+ case 0xff:
+ dst[j+0] = img.Pix[i+0]
+ dst[j+1] = img.Pix[i+1]
+ dst[j+2] = img.Pix[i+2]
+ default:
+ r16 := uint16(img.Pix[i+0])
+ g16 := uint16(img.Pix[i+1])
+ b16 := uint16(img.Pix[i+2])
+ a16 := uint16(a)
+ dst[j+0] = uint8(r16 * 0xff / a16)
+ dst[j+1] = uint8(g16 * 0xff / a16)
+ dst[j+2] = uint8(b16 * 0xff / a16)
+ }
+ dst[j+3] = a
+ j += 4
+ i += 4
+ }
+ }
+
+ case *image.RGBA64:
+ j := 0
+ for y := y1; y < y2; y++ {
+ i := y*img.Stride + x1*8
+ for x := x1; x < x2; x++ {
+ a := img.Pix[i+6]
+ switch a {
+ case 0:
+ dst[j+0] = 0
+ dst[j+1] = 0
+ dst[j+2] = 0
+ case 0xff:
+ dst[j+0] = img.Pix[i+0]
+ dst[j+1] = img.Pix[i+2]
+ dst[j+2] = img.Pix[i+4]
+ default:
+ r32 := uint32(img.Pix[i+0])<<8 | uint32(img.Pix[i+1])
+ g32 := uint32(img.Pix[i+2])<<8 | uint32(img.Pix[i+3])
+ b32 := uint32(img.Pix[i+4])<<8 | uint32(img.Pix[i+5])
+ a32 := uint32(img.Pix[i+6])<<8 | uint32(img.Pix[i+7])
+ dst[j+0] = uint8((r32 * 0xffff / a32) >> 8)
+ dst[j+1] = uint8((g32 * 0xffff / a32) >> 8)
+ dst[j+2] = uint8((b32 * 0xffff / a32) >> 8)
+ }
+ dst[j+3] = a
+ j += 4
+ i += 8
+ }
+ }
+
+ case *image.Gray:
+ j := 0
+ for y := y1; y < y2; y++ {
+ i := y*img.Stride + x1
+ for x := x1; x < x2; x++ {
+ c := img.Pix[i]
+ dst[j+0] = c
+ dst[j+1] = c
+ dst[j+2] = c
+ dst[j+3] = 0xff
+ j += 4
+ i++
+ }
+ }
+
+ case *image.Gray16:
+ j := 0
+ for y := y1; y < y2; y++ {
+ i := y*img.Stride + x1*2
+ for x := x1; x < x2; x++ {
+ c := img.Pix[i]
+ dst[j+0] = c
+ dst[j+1] = c
+ dst[j+2] = c
+ dst[j+3] = 0xff
+ j += 4
+ i += 2
+ }
+ }
+
+ case *image.YCbCr:
+ j := 0
+ x1 += img.Rect.Min.X
+ x2 += img.Rect.Min.X
+ y1 += img.Rect.Min.Y
+ y2 += img.Rect.Min.Y
+ for y := y1; y < y2; y++ {
+ iy := (y-img.Rect.Min.Y)*img.YStride + (x1 - img.Rect.Min.X)
+ for x := x1; x < x2; x++ {
+ var ic int
+ switch img.SubsampleRatio {
+ case image.YCbCrSubsampleRatio444:
+ ic = (y-img.Rect.Min.Y)*img.CStride + (x - img.Rect.Min.X)
+ case image.YCbCrSubsampleRatio422:
+ ic = (y-img.Rect.Min.Y)*img.CStride + (x/2 - img.Rect.Min.X/2)
+ case image.YCbCrSubsampleRatio420:
+ ic = (y/2-img.Rect.Min.Y/2)*img.CStride + (x/2 - img.Rect.Min.X/2)
+ case image.YCbCrSubsampleRatio440:
+ ic = (y/2-img.Rect.Min.Y/2)*img.CStride + (x - img.Rect.Min.X)
+ default:
+ ic = img.COffset(x, y)
+ }
+
+ yy := int(img.Y[iy])
+ cb := int(img.Cb[ic]) - 128
+ cr := int(img.Cr[ic]) - 128
+
+ r := (yy<<16 + 91881*cr + 1<<15) >> 16
+ if r > 0xff {
+ r = 0xff
+ } else if r < 0 {
+ r = 0
+ }
+
+ g := (yy<<16 - 22554*cb - 46802*cr + 1<<15) >> 16
+ if g > 0xff {
+ g = 0xff
+ } else if g < 0 {
+ g = 0
+ }
+
+ b := (yy<<16 + 116130*cb + 1<<15) >> 16
+ if b > 0xff {
+ b = 0xff
+ } else if b < 0 {
+ b = 0
+ }
+
+ dst[j+0] = uint8(r)
+ dst[j+1] = uint8(g)
+ dst[j+2] = uint8(b)
+ dst[j+3] = 0xff
+
+ iy++
+ j += 4
+ }
+ }
+
+ case *image.Paletted:
+ j := 0
+ for y := y1; y < y2; y++ {
+ i := y*img.Stride + x1
+ for x := x1; x < x2; x++ {
+ c := s.palette[img.Pix[i]]
+ dst[j+0] = c.R
+ dst[j+1] = c.G
+ dst[j+2] = c.B
+ dst[j+3] = c.A
+ j += 4
+ i++
+ }
+ }
+
+ default:
+ j := 0
+ b := s.image.Bounds()
+ x1 += b.Min.X
+ x2 += b.Min.X
+ y1 += b.Min.Y
+ y2 += b.Min.Y
+ for y := y1; y < y2; y++ {
+ for x := x1; x < x2; x++ {
+ r16, g16, b16, a16 := s.image.At(x, y).RGBA()
+ switch a16 {
+ case 0xffff:
+ dst[j+0] = uint8(r16 >> 8)
+ dst[j+1] = uint8(g16 >> 8)
+ dst[j+2] = uint8(b16 >> 8)
+ dst[j+3] = 0xff
+ case 0:
+ dst[j+0] = 0
+ dst[j+1] = 0
+ dst[j+2] = 0
+ dst[j+3] = 0
+ default:
+ dst[j+0] = uint8(((r16 * 0xffff) / a16) >> 8)
+ dst[j+1] = uint8(((g16 * 0xffff) / a16) >> 8)
+ dst[j+2] = uint8(((b16 * 0xffff) / a16) >> 8)
+ dst[j+3] = uint8(a16 >> 8)
+ }
+ j += 4
+ }
+ }
+ }
+}
diff --git a/vendor/github.com/disintegration/imaging/tools.go b/vendor/github.com/disintegration/imaging/tools.go
new file mode 100644
index 0000000..fae1fa1
--- /dev/null
+++ b/vendor/github.com/disintegration/imaging/tools.go
@@ -0,0 +1,213 @@
+package imaging
+
+import (
+ "image"
+ "math"
+)
+
+// Anchor is the anchor point for image alignment.
+type Anchor int
+
+// Anchor point positions.
+const (
+ Center Anchor = iota
+ TopLeft
+ Top
+ TopRight
+ Left
+ Right
+ BottomLeft
+ Bottom
+ BottomRight
+)
+
+func anchorPt(b image.Rectangle, w, h int, anchor Anchor) image.Point {
+ var x, y int
+ switch anchor {
+ case TopLeft:
+ x = b.Min.X
+ y = b.Min.Y
+ case Top:
+ x = b.Min.X + (b.Dx()-w)/2
+ y = b.Min.Y
+ case TopRight:
+ x = b.Max.X - w
+ y = b.Min.Y
+ case Left:
+ x = b.Min.X
+ y = b.Min.Y + (b.Dy()-h)/2
+ case Right:
+ x = b.Max.X - w
+ y = b.Min.Y + (b.Dy()-h)/2
+ case BottomLeft:
+ x = b.Min.X
+ y = b.Max.Y - h
+ case Bottom:
+ x = b.Min.X + (b.Dx()-w)/2
+ y = b.Max.Y - h
+ case BottomRight:
+ x = b.Max.X - w
+ y = b.Max.Y - h
+ default:
+ x = b.Min.X + (b.Dx()-w)/2
+ y = b.Min.Y + (b.Dy()-h)/2
+ }
+ return image.Pt(x, y)
+}
+
+// Crop cuts out a rectangular region with the specified bounds
+// from the image and returns the cropped image.
+func Crop(img image.Image, rect image.Rectangle) *image.NRGBA {
+ r := rect.Intersect(img.Bounds()).Sub(img.Bounds().Min)
+ if r.Empty() {
+ return &image.NRGBA{}
+ }
+ src := newScanner(img)
+ dst := image.NewNRGBA(image.Rect(0, 0, r.Dx(), r.Dy()))
+ rowSize := r.Dx() * 4
+ parallel(r.Min.Y, r.Max.Y, func(ys <-chan int) {
+ for y := range ys {
+ i := (y - r.Min.Y) * dst.Stride
+ src.scan(r.Min.X, y, r.Max.X, y+1, dst.Pix[i:i+rowSize])
+ }
+ })
+ return dst
+}
+
+// CropAnchor cuts out a rectangular region with the specified size
+// from the image using the specified anchor point and returns the cropped image.
+func CropAnchor(img image.Image, width, height int, anchor Anchor) *image.NRGBA {
+ srcBounds := img.Bounds()
+ pt := anchorPt(srcBounds, width, height, anchor)
+ r := image.Rect(0, 0, width, height).Add(pt)
+ b := srcBounds.Intersect(r)
+ return Crop(img, b)
+}
+
+// CropCenter cuts out a rectangular region with the specified size
+// from the center of the image and returns the cropped image.
+func CropCenter(img image.Image, width, height int) *image.NRGBA {
+ return CropAnchor(img, width, height, Center)
+}
+
+// Paste pastes the img image to the background image at the specified position and returns the combined image.
+func Paste(background, img image.Image, pos image.Point) *image.NRGBA {
+ dst := Clone(background)
+ pos = pos.Sub(background.Bounds().Min)
+ pasteRect := image.Rectangle{Min: pos, Max: pos.Add(img.Bounds().Size())}
+ interRect := pasteRect.Intersect(dst.Bounds())
+ if interRect.Empty() {
+ return dst
+ }
+ src := newScanner(img)
+ parallel(interRect.Min.Y, interRect.Max.Y, func(ys <-chan int) {
+ for y := range ys {
+ x1 := interRect.Min.X - pasteRect.Min.X
+ x2 := interRect.Max.X - pasteRect.Min.X
+ y1 := y - pasteRect.Min.Y
+ y2 := y1 + 1
+ i1 := y*dst.Stride + interRect.Min.X*4
+ i2 := i1 + interRect.Dx()*4
+ src.scan(x1, y1, x2, y2, dst.Pix[i1:i2])
+ }
+ })
+ return dst
+}
+
+// PasteCenter pastes the img image to the center of the background image and returns the combined image.
+func PasteCenter(background, img image.Image) *image.NRGBA {
+ bgBounds := background.Bounds()
+ bgW := bgBounds.Dx()
+ bgH := bgBounds.Dy()
+ bgMinX := bgBounds.Min.X
+ bgMinY := bgBounds.Min.Y
+
+ centerX := bgMinX + bgW/2
+ centerY := bgMinY + bgH/2
+
+ x0 := centerX - img.Bounds().Dx()/2
+ y0 := centerY - img.Bounds().Dy()/2
+
+ return Paste(background, img, image.Pt(x0, y0))
+}
+
+// Overlay draws the img image over the background image at given position
+// and returns the combined image. Opacity parameter is the opacity of the img
+// image layer, used to compose the images, it must be from 0.0 to 1.0.
+//
+// Usage examples:
+//
+// // Draw spriteImage over backgroundImage at the given position (x=50, y=50).
+// dstImage := imaging.Overlay(backgroundImage, spriteImage, image.Pt(50, 50), 1.0)
+//
+// // Blend two opaque images of the same size.
+// dstImage := imaging.Overlay(imageOne, imageTwo, image.Pt(0, 0), 0.5)
+//
+func Overlay(background, img image.Image, pos image.Point, opacity float64) *image.NRGBA {
+ opacity = math.Min(math.Max(opacity, 0.0), 1.0) // Ensure 0.0 <= opacity <= 1.0.
+ dst := Clone(background)
+ pos = pos.Sub(background.Bounds().Min)
+ pasteRect := image.Rectangle{Min: pos, Max: pos.Add(img.Bounds().Size())}
+ interRect := pasteRect.Intersect(dst.Bounds())
+ if interRect.Empty() {
+ return dst
+ }
+ src := newScanner(img)
+ parallel(interRect.Min.Y, interRect.Max.Y, func(ys <-chan int) {
+ scanLine := make([]uint8, interRect.Dx()*4)
+ for y := range ys {
+ x1 := interRect.Min.X - pasteRect.Min.X
+ x2 := interRect.Max.X - pasteRect.Min.X
+ y1 := y - pasteRect.Min.Y
+ y2 := y1 + 1
+ src.scan(x1, y1, x2, y2, scanLine)
+ i := y*dst.Stride + interRect.Min.X*4
+ j := 0
+ for x := interRect.Min.X; x < interRect.Max.X; x++ {
+ r1 := float64(dst.Pix[i+0])
+ g1 := float64(dst.Pix[i+1])
+ b1 := float64(dst.Pix[i+2])
+ a1 := float64(dst.Pix[i+3])
+
+ r2 := float64(scanLine[j+0])
+ g2 := float64(scanLine[j+1])
+ b2 := float64(scanLine[j+2])
+ a2 := float64(scanLine[j+3])
+
+ coef2 := opacity * a2 / 255
+ coef1 := (1 - coef2) * a1 / 255
+ coefSum := coef1 + coef2
+ coef1 /= coefSum
+ coef2 /= coefSum
+
+ dst.Pix[i+0] = uint8(r1*coef1 + r2*coef2)
+ dst.Pix[i+1] = uint8(g1*coef1 + g2*coef2)
+ dst.Pix[i+2] = uint8(b1*coef1 + b2*coef2)
+ dst.Pix[i+3] = uint8(math.Min(a1+a2*opacity*(255-a1)/255, 255))
+
+ i += 4
+ j += 4
+ }
+ }
+ })
+ return dst
+}
+
+// OverlayCenter overlays the img image to the center of the background image and
+// returns the combined image. Opacity parameter is the opacity of the img
+// image layer, used to compose the images, it must be from 0.0 to 1.0.
+func OverlayCenter(background, img image.Image, opacity float64) *image.NRGBA {
+ bgBounds := background.Bounds()
+ bgW := bgBounds.Dx()
+ bgH := bgBounds.Dy()
+ bgMinX := bgBounds.Min.X
+ bgMinY := bgBounds.Min.Y
+
+ centerX := bgMinX + bgW/2
+ centerY := bgMinY + bgH/2
+
+ x0 := centerX - img.Bounds().Dx()/2
+ y0 := centerY - img.Bounds().Dy()/2
+
+ return Overlay(background, img, image.Point{x0, y0}, opacity)
+}
diff --git a/vendor/github.com/disintegration/imaging/transform.go b/vendor/github.com/disintegration/imaging/transform.go
new file mode 100644
index 0000000..d788d0d
--- /dev/null
+++ b/vendor/github.com/disintegration/imaging/transform.go
@@ -0,0 +1,271 @@
+package imaging
+
+import (
+ "image"
+ "image/color"
+ "math"
+)
+
+// FlipH flips the image horizontally (from left to right) and returns the transformed image.
+func FlipH(img image.Image) *image.NRGBA {
+ src := newScanner(img)
+ dstW := src.w
+ dstH := src.h
+ rowSize := dstW * 4
+ dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
+ parallel(0, dstH, func(ys <-chan int) {
+ for dstY := range ys {
+ i := dstY * dst.Stride
+ srcY := dstY
+ src.scan(0, srcY, src.w, srcY+1, dst.Pix[i:i+rowSize])
+ reverse(dst.Pix[i : i+rowSize])
+ }
+ })
+ return dst
+}
+
+// FlipV flips the image vertically (from top to bottom) and returns the transformed image.
+func FlipV(img image.Image) *image.NRGBA {
+ src := newScanner(img)
+ dstW := src.w
+ dstH := src.h
+ rowSize := dstW * 4
+ dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
+ parallel(0, dstH, func(ys <-chan int) {
+ for dstY := range ys {
+ i := dstY * dst.Stride
+ srcY := dstH - dstY - 1
+ src.scan(0, srcY, src.w, srcY+1, dst.Pix[i:i+rowSize])
+ }
+ })
+ return dst
+}
+
+// Transpose flips the image horizontally and rotates 90 degrees counter-clockwise.
+func Transpose(img image.Image) *image.NRGBA {
+ src := newScanner(img)
+ dstW := src.h
+ dstH := src.w
+ rowSize := dstW * 4
+ dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
+ parallel(0, dstH, func(ys <-chan int) {
+ for dstY := range ys {
+ i := dstY * dst.Stride
+ srcX := dstY
+ src.scan(srcX, 0, srcX+1, src.h, dst.Pix[i:i+rowSize])
+ }
+ })
+ return dst
+}
+
+// Transverse flips the image vertically and rotates 90 degrees counter-clockwise.
+func Transverse(img image.Image) *image.NRGBA {
+ src := newScanner(img)
+ dstW := src.h
+ dstH := src.w
+ rowSize := dstW * 4
+ dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
+ parallel(0, dstH, func(ys <-chan int) {
+ for dstY := range ys {
+ i := dstY * dst.Stride
+ srcX := dstH - dstY - 1
+ src.scan(srcX, 0, srcX+1, src.h, dst.Pix[i:i+rowSize])
+ reverse(dst.Pix[i : i+rowSize])
+ }
+ })
+ return dst
+}
+
+// Rotate90 rotates the image 90 degrees counter-clockwise and returns the transformed image.
+func Rotate90(img image.Image) *image.NRGBA {
+ src := newScanner(img)
+ dstW := src.h
+ dstH := src.w
+ rowSize := dstW * 4
+ dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
+ parallel(0, dstH, func(ys <-chan int) {
+ for dstY := range ys {
+ i := dstY * dst.Stride
+ srcX := dstH - dstY - 1
+ src.scan(srcX, 0, srcX+1, src.h, dst.Pix[i:i+rowSize])
+ }
+ })
+ return dst
+}
+
+// Rotate180 rotates the image 180 degrees counter-clockwise and returns the transformed image.
+func Rotate180(img image.Image) *image.NRGBA {
+ src := newScanner(img)
+ dstW := src.w
+ dstH := src.h
+ rowSize := dstW * 4
+ dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
+ parallel(0, dstH, func(ys <-chan int) {
+ for dstY := range ys {
+ i := dstY * dst.Stride
+ srcY := dstH - dstY - 1
+ src.scan(0, srcY, src.w, srcY+1, dst.Pix[i:i+rowSize])
+ reverse(dst.Pix[i : i+rowSize])
+ }
+ })
+ return dst
+}
+
+// Rotate270 rotates the image 270 degrees counter-clockwise and returns the transformed image.
+func Rotate270(img image.Image) *image.NRGBA {
+ src := newScanner(img)
+ dstW := src.h
+ dstH := src.w
+ rowSize := dstW * 4
+ dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
+ parallel(0, dstH, func(ys <-chan int) {
+ for dstY := range ys {
+ i := dstY * dst.Stride
+ srcX := dstY
+ src.scan(srcX, 0, srcX+1, src.h, dst.Pix[i:i+rowSize])
+ reverse(dst.Pix[i : i+rowSize])
+ }
+ })
+ return dst
+}
+
+// Rotate rotates an image by the given angle counter-clockwise .
+// The angle parameter is the rotation angle in degrees.
+// The bgColor parameter specifies the color of the uncovered zone after the rotation.
+func Rotate(img image.Image, angle float64, bgColor color.Color) *image.NRGBA {
+ angle = angle - math.Floor(angle/360)*360
+
+ switch angle {
+ case 0:
+ return Clone(img)
+ case 90:
+ return Rotate90(img)
+ case 180:
+ return Rotate180(img)
+ case 270:
+ return Rotate270(img)
+ }
+
+ src := toNRGBA(img)
+ srcW := src.Bounds().Max.X
+ srcH := src.Bounds().Max.Y
+ dstW, dstH := rotatedSize(srcW, srcH, angle)
+ dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
+
+ if dstW <= 0 || dstH <= 0 {
+ return dst
+ }
+
+ srcXOff := float64(srcW)/2 - 0.5
+ srcYOff := float64(srcH)/2 - 0.5
+ dstXOff := float64(dstW)/2 - 0.5
+ dstYOff := float64(dstH)/2 - 0.5
+
+ bgColorNRGBA := color.NRGBAModel.Convert(bgColor).(color.NRGBA)
+ sin, cos := math.Sincos(math.Pi * angle / 180)
+
+ parallel(0, dstH, func(ys <-chan int) {
+ for dstY := range ys {
+ for dstX := 0; dstX < dstW; dstX++ {
+ xf, yf := rotatePoint(float64(dstX)-dstXOff, float64(dstY)-dstYOff, sin, cos)
+ xf, yf = xf+srcXOff, yf+srcYOff
+ interpolatePoint(dst, dstX, dstY, src, xf, yf, bgColorNRGBA)
+ }
+ }
+ })
+
+ return dst
+}
+
+func rotatePoint(x, y, sin, cos float64) (float64, float64) {
+ return x*cos - y*sin, x*sin + y*cos
+}
+
+func rotatedSize(w, h int, angle float64) (int, int) {
+ if w <= 0 || h <= 0 {
+ return 0, 0
+ }
+
+ sin, cos := math.Sincos(math.Pi * angle / 180)
+ x1, y1 := rotatePoint(float64(w-1), 0, sin, cos)
+ x2, y2 := rotatePoint(float64(w-1), float64(h-1), sin, cos)
+ x3, y3 := rotatePoint(0, float64(h-1), sin, cos)
+
+ minx := math.Min(x1, math.Min(x2, math.Min(x3, 0)))
+ maxx := math.Max(x1, math.Max(x2, math.Max(x3, 0)))
+ miny := math.Min(y1, math.Min(y2, math.Min(y3, 0)))
+ maxy := math.Max(y1, math.Max(y2, math.Max(y3, 0)))
+
+ neww := maxx - minx + 1
+ if neww-math.Floor(neww) > 0.1 {
+ neww++
+ }
+ newh := maxy - miny + 1
+ if newh-math.Floor(newh) > 0.1 {
+ newh++
+ }
+
+ return int(neww), int(newh)
+}
+
+func interpolatePoint(dst *image.NRGBA, dstX, dstY int, src *image.NRGBA, xf, yf float64, bgColor color.NRGBA) {
+ dstIndex := dstY*dst.Stride + dstX*4
+
+ x0 := int(math.Floor(xf))
+ y0 := int(math.Floor(yf))
+ bounds := src.Bounds()
+ if !image.Pt(x0, y0).In(image.Rect(bounds.Min.X-1, bounds.Min.Y-1, bounds.Max.X, bounds.Max.Y)) {
+ dst.Pix[dstIndex+0] = bgColor.R
+ dst.Pix[dstIndex+1] = bgColor.G
+ dst.Pix[dstIndex+2] = bgColor.B
+ dst.Pix[dstIndex+3] = bgColor.A
+ return
+ }
+
+ xq := xf - float64(x0)
+ yq := yf - float64(y0)
+
+ var pxs [4]color.NRGBA
+ var cfs [4]float64
+
+ for i := 0; i < 2; i++ {
+ for j := 0; j < 2; j++ {
+ k := i*2 + j
+ pt := image.Pt(x0+j, y0+i)
+ if pt.In(bounds) {
+ l := pt.Y*src.Stride + pt.X*4
+ pxs[k].R = src.Pix[l+0]
+ pxs[k].G = src.Pix[l+1]
+ pxs[k].B = src.Pix[l+2]
+ pxs[k].A = src.Pix[l+3]
+ } else {
+ pxs[k] = bgColor
+ }
+ }
+ }
+
+ cfs[0] = (1 - xq) * (1 - yq)
+ cfs[1] = xq * (1 - yq)
+ cfs[2] = (1 - xq) * yq
+ cfs[3] = xq * yq
+
+ var r, g, b, a float64
+ for i := range pxs {
+ wa := float64(pxs[i].A) * cfs[i]
+ r += float64(pxs[i].R) * wa
+ g += float64(pxs[i].G) * wa
+ b += float64(pxs[i].B) * wa
+ a += wa
+ }
+
+ if a != 0 {
+ r /= a
+ g /= a
+ b /= a
+ }
+
+ dst.Pix[dstIndex+0] = clamp(r)
+ dst.Pix[dstIndex+1] = clamp(g)
+ dst.Pix[dstIndex+2] = clamp(b)
+ dst.Pix[dstIndex+3] = clamp(a)
+}
diff --git a/vendor/github.com/disintegration/imaging/utils.go b/vendor/github.com/disintegration/imaging/utils.go
new file mode 100644
index 0000000..3b6ad2e
--- /dev/null
+++ b/vendor/github.com/disintegration/imaging/utils.go
@@ -0,0 +1,83 @@
+package imaging
+
+import (
+ "image"
+ "runtime"
+ "sync"
+)
+
+// parallel processes the data in separate goroutines.
+func parallel(start, stop int, fn func(<-chan int)) {
+ count := stop - start
+ if count < 1 {
+ return
+ }
+
+ procs := runtime.GOMAXPROCS(0)
+ if procs > count {
+ procs = count
+ }
+
+ c := make(chan int, count)
+ for i := start; i < stop; i++ {
+ c <- i
+ }
+ close(c)
+
+ var wg sync.WaitGroup
+ for i := 0; i < procs; i++ {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ fn(c)
+ }()
+ }
+ wg.Wait()
+}
+
+// absint returns the absolute value of i.
+func absint(i int) int {
+ if i < 0 {
+ return -i
+ }
+ return i
+}
+
+// clamp rounds and clamps float64 value to fit into uint8.
+func clamp(x float64) uint8 {
+ v := int64(x + 0.5)
+ if v > 255 {
+ return 255
+ }
+ if v > 0 {
+ return uint8(v)
+ }
+ return 0
+}
+
+func reverse(pix []uint8) {
+ if len(pix) <= 4 {
+ return
+ }
+ i := 0
+ j := len(pix) - 4
+ for i < j {
+ pix[i+0], pix[j+0] = pix[j+0], pix[i+0]
+ pix[i+1], pix[j+1] = pix[j+1], pix[i+1]
+ pix[i+2], pix[j+2] = pix[j+2], pix[i+2]
+ pix[i+3], pix[j+3] = pix[j+3], pix[i+3]
+ i += 4
+ j -= 4
+ }
+}
+
+func toNRGBA(img image.Image) *image.NRGBA {
+ if img, ok := img.(*image.NRGBA); ok {
+ return &image.NRGBA{
+ Pix: img.Pix,
+ Stride: img.Stride,
+ Rect: img.Rect.Sub(img.Rect.Min),
+ }
+ }
+ return Clone(img)
+}