vendor: add all of our dependencies

This commit is contained in:
Will Norris 2015-11-26 12:41:45 -08:00
parent d9e4cf282a
commit 69a7ccce71
39 changed files with 8202 additions and 0 deletions

21
vendor/github.com/disintegration/imaging/LICENSE generated vendored Normal file
View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2012-2014 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.

194
vendor/github.com/disintegration/imaging/README.md generated vendored Normal file
View file

@ -0,0 +1,194 @@
# Imaging
Package imaging provides basic image manipulation functions (resize, rotate, flip, crop, etc.).
This package is based on the standard Go image package and works best along with it.
Image manipulation functions provided by the package take 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
Imaging requires Go version 1.2 or greater.
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, but it's 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. Will be resized from 512x512px to 128x128px.
![srcImage](http://disintegration.github.io/imaging/in_lena_bw_512.png)
Filter | Resize result
---|---
`imaging.NearestNeighbor` | ![dstImage](http://disintegration.github.io/imaging/out_resize_down_nearest.png)
`imaging.Box` | ![dstImage](http://disintegration.github.io/imaging/out_resize_down_box.png)
`imaging.Linear` | ![dstImage](http://disintegration.github.io/imaging/out_resize_down_linear.png)
`imaging.MitchellNetravali` | ![dstImage](http://disintegration.github.io/imaging/out_resize_down_mitchell.png)
`imaging.CatmullRom` | ![dstImage](http://disintegration.github.io/imaging/out_resize_down_catrom.png)
`imaging.Gaussian` | ![dstImage](http://disintegration.github.io/imaging/out_resize_down_gaussian.png)
`imaging.Lanczos` | ![dstImage](http://disintegration.github.io/imaging/out_resize_down_lanczos.png)
**Resize functions comparison**
Original image:
![srcImage](http://disintegration.github.io/imaging/in.jpg)
Resize the image to width=100px and height=100px:
```go
dstImage := imaging.Resize(srcImage, 100, 100, imaging.Lanczos)
```
![dstImage](http://disintegration.github.io/imaging/out-comp-resize.jpg)
Resize the image to width=100px preserving the aspect ratio:
```go
dstImage := imaging.Resize(srcImage, 100, 0, imaging.Lanczos)
```
![dstImage](http://disintegration.github.io/imaging/out-comp-fit.jpg)
Resize the image to fit the 100x100px boundng box preserving the aspect ratio:
```go
dstImage := imaging.Fit(srcImage, 100, 100, imaging.Lanczos)
```
![dstImage](http://disintegration.github.io/imaging/out-comp-fit.jpg)
Resize and crop the image with a center anchor point to fill the 100x100px area:
```go
dstImage := imaging.Fill(srcImage, 100, 100, imaging.Center, imaging.Lanczos)
```
![dstImage](http://disintegration.github.io/imaging/out-comp-fill.jpg)
### 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](http://disintegration.github.io/imaging/in_lena_bw_128.png) | ![dstImage](http://disintegration.github.io/imaging/out_blur_0.5.png) | ![dstImage](http://disintegration.github.io/imaging/out_blur_1.5.png)
### Sharpening
```go
dstImage := imaging.Sharpen(srcImage, 0.5)
```
Uses gaussian function internally. Sigma parameter allows to control the strength of the sharpening effect.
Original image | Sigma = 0.5 | Sigma = 1.5
---|---|---
![srcImage](http://disintegration.github.io/imaging/in_lena_bw_128.png) | ![dstImage](http://disintegration.github.io/imaging/out_sharpen_0.5.png) | ![dstImage](http://disintegration.github.io/imaging/out_sharpen_1.5.png)
### Gamma correction
```go
dstImage := imaging.AdjustGamma(srcImage, 0.75)
```
Original image | Gamma = 0.75 | Gamma = 1.25
---|---|---
![srcImage](http://disintegration.github.io/imaging/in_lena_bw_128.png) | ![dstImage](http://disintegration.github.io/imaging/out_gamma_0.75.png) | ![dstImage](http://disintegration.github.io/imaging/out_gamma_1.25.png)
### Contrast adjustment
```go
dstImage := imaging.AdjustContrast(srcImage, 20)
```
Original image | Contrast = 20 | Contrast = -20
---|---|---
![srcImage](http://disintegration.github.io/imaging/in_lena_bw_128.png) | ![dstImage](http://disintegration.github.io/imaging/out_contrast_p20.png) | ![dstImage](http://disintegration.github.io/imaging/out_contrast_m20.png)
### Brightness adjustment
```go
dstImage := imaging.AdjustBrightness(srcImage, 20)
```
Original image | Brightness = 20 | Brightness = -20
---|---|---
![srcImage](http://disintegration.github.io/imaging/in_lena_bw_128.png) | ![dstImage](http://disintegration.github.io/imaging/out_brightness_p20.png) | ![dstImage](http://disintegration.github.io/imaging/out_brightness_m20.png)
### Complete code example
Here is the code example that loads several images, makes thumbnails of them
and combines them together side-by-side.
```go
package main
import (
"image"
"image/color"
"github.com/disintegration/imaging"
)
func main() {
// input files
files := []string{"01.jpg", "02.jpg", "03.jpg"}
// load images and make 100x100 thumbnails of them
var thumbnails []image.Image
for _, file := range files {
img, err := imaging.Open(file)
if err != nil {
panic(err)
}
thumb := imaging.Thumbnail(img, 100, 100, imaging.CatmullRom)
thumbnails = append(thumbnails, thumb)
}
// create a new blank image
dst := imaging.New(100*len(thumbnails), 100, color.NRGBA{0, 0, 0, 0})
// paste thumbnails into the new image side by side
for i, thumb := range thumbnails {
dst = imaging.Paste(dst, thumb, image.Pt(i*100, 0))
}
// save the combined image to file
err := imaging.Save(dst, "dst.jpg")
if err != nil {
panic(err)
}
}
```

200
vendor/github.com/disintegration/imaging/adjust.go generated vendored Normal file
View file

@ -0,0 +1,200 @@
package imaging
import (
"image"
"image/color"
"math"
)
// 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 := toNRGBA(img)
width := src.Bounds().Max.X
height := src.Bounds().Max.Y
dst := image.NewNRGBA(image.Rect(0, 0, width, height))
parallel(height, func(partStart, partEnd int) {
for y := partStart; y < partEnd; y++ {
for x := 0; x < width; x++ {
i := y*src.Stride + x*4
j := y*dst.Stride + x*4
r := src.Pix[i+0]
g := src.Pix[i+1]
b := src.Pix[i+2]
a := src.Pix[i+3]
c := fn(color.NRGBA{r, g, b, a})
dst.Pix[j+0] = c.R
dst.Pix[j+1] = c.G
dst.Pix[j+2] = c.B
dst.Pix[j+3] = c.A
}
}
})
return dst
}
// 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)
}
fn := func(c color.NRGBA) color.NRGBA {
return color.NRGBA{lut[c.R], lut[c.G], lut[c.B], c.A}
}
return AdjustFunc(img, fn)
}
func sigmoid(a, b, x float64) float64 {
return 1 / (1 + math.Exp(b*(a-x)))
}
// 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)
}
}
fn := func(c color.NRGBA) color.NRGBA {
return color.NRGBA{lut[c.R], lut[c.G], lut[c.B], c.A}
}
return AdjustFunc(img, fn)
}
// 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 grey 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
}
}
fn := func(c color.NRGBA) color.NRGBA {
return color.NRGBA{lut[c.R], lut[c.G], lut[c.B], c.A}
}
return AdjustFunc(img, fn)
}
// 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)
}
fn := func(c color.NRGBA) color.NRGBA {
return color.NRGBA{lut[c.R], lut[c.G], lut[c.B], c.A}
}
return AdjustFunc(img, fn)
}
// Grayscale produces grayscale version of the image.
func Grayscale(img image.Image) *image.NRGBA {
fn := func(c color.NRGBA) color.NRGBA {
f := 0.299*float64(c.R) + 0.587*float64(c.G) + 0.114*float64(c.B)
y := uint8(f + 0.5)
return color.NRGBA{y, y, y, c.A}
}
return AdjustFunc(img, fn)
}
// Invert produces inverted (negated) version of the image.
func Invert(img image.Image) *image.NRGBA {
fn := func(c color.NRGBA) color.NRGBA {
return color.NRGBA{255 - c.R, 255 - c.G, 255 - c.B, c.A}
}
return AdjustFunc(img, fn)
}

187
vendor/github.com/disintegration/imaging/effects.go generated vendored Normal file
View file

@ -0,0 +1,187 @@
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 {
// sigma parameter must be positive!
return Clone(img)
}
src := toNRGBA(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)
}
var dst *image.NRGBA
dst = blurHorizontal(src, kernel)
dst = blurVertical(dst, kernel)
return dst
}
func blurHorizontal(src *image.NRGBA, kernel []float64) *image.NRGBA {
radius := len(kernel) - 1
width := src.Bounds().Max.X
height := src.Bounds().Max.Y
dst := image.NewNRGBA(image.Rect(0, 0, width, height))
parallel(width, func(partStart, partEnd int) {
for x := partStart; x < partEnd; x++ {
start := x - radius
if start < 0 {
start = 0
}
end := x + radius
if end > width-1 {
end = width - 1
}
weightSum := 0.0
for ix := start; ix <= end; ix++ {
weightSum += kernel[absint(x-ix)]
}
for y := 0; y < height; y++ {
r, g, b, a := 0.0, 0.0, 0.0, 0.0
for ix := start; ix <= end; ix++ {
weight := kernel[absint(x-ix)]
i := y*src.Stride + ix*4
r += float64(src.Pix[i+0]) * weight
g += float64(src.Pix[i+1]) * weight
b += float64(src.Pix[i+2]) * weight
a += float64(src.Pix[i+3]) * weight
}
r = math.Min(math.Max(r/weightSum, 0.0), 255.0)
g = math.Min(math.Max(g/weightSum, 0.0), 255.0)
b = math.Min(math.Max(b/weightSum, 0.0), 255.0)
a = math.Min(math.Max(a/weightSum, 0.0), 255.0)
j := y*dst.Stride + x*4
dst.Pix[j+0] = uint8(r + 0.5)
dst.Pix[j+1] = uint8(g + 0.5)
dst.Pix[j+2] = uint8(b + 0.5)
dst.Pix[j+3] = uint8(a + 0.5)
}
}
})
return dst
}
func blurVertical(src *image.NRGBA, kernel []float64) *image.NRGBA {
radius := len(kernel) - 1
width := src.Bounds().Max.X
height := src.Bounds().Max.Y
dst := image.NewNRGBA(image.Rect(0, 0, width, height))
parallel(height, func(partStart, partEnd int) {
for y := partStart; y < partEnd; y++ {
start := y - radius
if start < 0 {
start = 0
}
end := y + radius
if end > height-1 {
end = height - 1
}
weightSum := 0.0
for iy := start; iy <= end; iy++ {
weightSum += kernel[absint(y-iy)]
}
for x := 0; x < width; x++ {
r, g, b, a := 0.0, 0.0, 0.0, 0.0
for iy := start; iy <= end; iy++ {
weight := kernel[absint(y-iy)]
i := iy*src.Stride + x*4
r += float64(src.Pix[i+0]) * weight
g += float64(src.Pix[i+1]) * weight
b += float64(src.Pix[i+2]) * weight
a += float64(src.Pix[i+3]) * weight
}
r = math.Min(math.Max(r/weightSum, 0.0), 255.0)
g = math.Min(math.Max(g/weightSum, 0.0), 255.0)
b = math.Min(math.Max(b/weightSum, 0.0), 255.0)
a = math.Min(math.Max(a/weightSum, 0.0), 255.0)
j := y*dst.Stride + x*4
dst.Pix[j+0] = uint8(r + 0.5)
dst.Pix[j+1] = uint8(g + 0.5)
dst.Pix[j+2] = uint8(b + 0.5)
dst.Pix[j+3] = uint8(a + 0.5)
}
}
})
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 {
// sigma parameter must be positive!
return Clone(img)
}
src := toNRGBA(img)
blurred := Blur(img, sigma)
width := src.Bounds().Max.X
height := src.Bounds().Max.Y
dst := image.NewNRGBA(image.Rect(0, 0, width, height))
parallel(height, func(partStart, partEnd int) {
for y := partStart; y < partEnd; y++ {
for x := 0; x < width; x++ {
i := y*src.Stride + x*4
for j := 0; j < 4; j++ {
k := i + j
val := int(src.Pix[k]) + (int(src.Pix[k]) - int(blurred.Pix[k]))
if val < 0 {
val = 0
} else if val > 255 {
val = 255
}
dst.Pix[k] = uint8(val)
}
}
}
})
return dst
}

400
vendor/github.com/disintegration/imaging/helpers.go generated vendored Normal file
View file

@ -0,0 +1,400 @@
/*
Package imaging provides basic image manipulation functions (resize, rotate, flip, crop, etc.).
This package is based on the standard Go image package and works best along with it.
Image manipulation functions provided by the package take 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
import (
"errors"
"image"
"image/color"
"image/gif"
"image/jpeg"
"image/png"
"io"
"os"
"path/filepath"
"strings"
"golang.org/x/image/bmp"
"golang.org/x/image/tiff"
)
type Format int
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 = errors.New("imaging: unsupported image format")
)
// Decode reads an image from r.
func Decode(r io.Reader) (image.Image, error) {
img, _, err := image.Decode(r)
if err != nil {
return nil, err
}
return toNRGBA(img), nil
}
// Open loads an image from file
func Open(filename string) (image.Image, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
img, err := Decode(file)
return img, err
}
// 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) error {
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: 95})
} else {
err = jpeg.Encode(w, img, &jpeg.Options{Quality: 95})
}
case PNG:
err = png.Encode(w, img)
case GIF:
err = gif.Encode(w, img, &gif.Options{NumColors: 256})
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.
func Save(img image.Image, filename string) (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 := os.Create(filename)
if err != nil {
return err
}
defer file.Close()
return Encode(file, img, f)
}
// 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
}
cs := []uint8{c.R, c.G, c.B, c.A}
// fill the first row
for x := 0; x < width; x++ {
copy(dst.Pix[x*4:(x+1)*4], cs)
}
// copy the first row to other rows
for y := 1; y < height; y++ {
copy(dst.Pix[y*dst.Stride:y*dst.Stride+width*4], dst.Pix[0:width*4])
}
return dst
}
// Clone returns a copy of the given image.
func Clone(img image.Image) *image.NRGBA {
srcBounds := img.Bounds()
srcMinX := srcBounds.Min.X
srcMinY := srcBounds.Min.Y
dstBounds := srcBounds.Sub(srcBounds.Min)
dstW := dstBounds.Dx()
dstH := dstBounds.Dy()
dst := image.NewNRGBA(dstBounds)
switch src := img.(type) {
case *image.NRGBA:
rowSize := srcBounds.Dx() * 4
parallel(dstH, func(partStart, partEnd int) {
for dstY := partStart; dstY < partEnd; dstY++ {
di := dst.PixOffset(0, dstY)
si := src.PixOffset(srcMinX, srcMinY+dstY)
copy(dst.Pix[di:di+rowSize], src.Pix[si:si+rowSize])
}
})
case *image.NRGBA64:
parallel(dstH, func(partStart, partEnd int) {
for dstY := partStart; dstY < partEnd; dstY++ {
di := dst.PixOffset(0, dstY)
si := src.PixOffset(srcMinX, srcMinY+dstY)
for dstX := 0; dstX < dstW; dstX++ {
dst.Pix[di+0] = src.Pix[si+0]
dst.Pix[di+1] = src.Pix[si+2]
dst.Pix[di+2] = src.Pix[si+4]
dst.Pix[di+3] = src.Pix[si+6]
di += 4
si += 8
}
}
})
case *image.RGBA:
parallel(dstH, func(partStart, partEnd int) {
for dstY := partStart; dstY < partEnd; dstY++ {
di := dst.PixOffset(0, dstY)
si := src.PixOffset(srcMinX, srcMinY+dstY)
for dstX := 0; dstX < dstW; dstX++ {
a := src.Pix[si+3]
dst.Pix[di+3] = a
switch a {
case 0:
dst.Pix[di+0] = 0
dst.Pix[di+1] = 0
dst.Pix[di+2] = 0
case 0xff:
dst.Pix[di+0] = src.Pix[si+0]
dst.Pix[di+1] = src.Pix[si+1]
dst.Pix[di+2] = src.Pix[si+2]
default:
var tmp uint16
tmp = uint16(src.Pix[si+0]) * 0xff / uint16(a)
dst.Pix[di+0] = uint8(tmp)
tmp = uint16(src.Pix[si+1]) * 0xff / uint16(a)
dst.Pix[di+1] = uint8(tmp)
tmp = uint16(src.Pix[si+2]) * 0xff / uint16(a)
dst.Pix[di+2] = uint8(tmp)
}
di += 4
si += 4
}
}
})
case *image.RGBA64:
parallel(dstH, func(partStart, partEnd int) {
for dstY := partStart; dstY < partEnd; dstY++ {
di := dst.PixOffset(0, dstY)
si := src.PixOffset(srcMinX, srcMinY+dstY)
for dstX := 0; dstX < dstW; dstX++ {
a := src.Pix[si+6]
dst.Pix[di+3] = a
switch a {
case 0:
dst.Pix[di+0] = 0
dst.Pix[di+1] = 0
dst.Pix[di+2] = 0
case 0xff:
dst.Pix[di+0] = src.Pix[si+0]
dst.Pix[di+1] = src.Pix[si+2]
dst.Pix[di+2] = src.Pix[si+4]
default:
var tmp uint16
tmp = uint16(src.Pix[si+0]) * 0xff / uint16(a)
dst.Pix[di+0] = uint8(tmp)
tmp = uint16(src.Pix[si+2]) * 0xff / uint16(a)
dst.Pix[di+1] = uint8(tmp)
tmp = uint16(src.Pix[si+4]) * 0xff / uint16(a)
dst.Pix[di+2] = uint8(tmp)
}
di += 4
si += 8
}
}
})
case *image.Gray:
parallel(dstH, func(partStart, partEnd int) {
for dstY := partStart; dstY < partEnd; dstY++ {
di := dst.PixOffset(0, dstY)
si := src.PixOffset(srcMinX, srcMinY+dstY)
for dstX := 0; dstX < dstW; dstX++ {
c := src.Pix[si]
dst.Pix[di+0] = c
dst.Pix[di+1] = c
dst.Pix[di+2] = c
dst.Pix[di+3] = 0xff
di += 4
si += 1
}
}
})
case *image.Gray16:
parallel(dstH, func(partStart, partEnd int) {
for dstY := partStart; dstY < partEnd; dstY++ {
di := dst.PixOffset(0, dstY)
si := src.PixOffset(srcMinX, srcMinY+dstY)
for dstX := 0; dstX < dstW; dstX++ {
c := src.Pix[si]
dst.Pix[di+0] = c
dst.Pix[di+1] = c
dst.Pix[di+2] = c
dst.Pix[di+3] = 0xff
di += 4
si += 2
}
}
})
case *image.YCbCr:
parallel(dstH, func(partStart, partEnd int) {
for dstY := partStart; dstY < partEnd; dstY++ {
di := dst.PixOffset(0, dstY)
for dstX := 0; dstX < dstW; dstX++ {
srcX := srcMinX + dstX
srcY := srcMinY + dstY
siy := src.YOffset(srcX, srcY)
sic := src.COffset(srcX, srcY)
r, g, b := color.YCbCrToRGB(src.Y[siy], src.Cb[sic], src.Cr[sic])
dst.Pix[di+0] = r
dst.Pix[di+1] = g
dst.Pix[di+2] = b
dst.Pix[di+3] = 0xff
di += 4
}
}
})
case *image.Paletted:
plen := len(src.Palette)
pnew := make([]color.NRGBA, plen)
for i := 0; i < plen; i++ {
pnew[i] = color.NRGBAModel.Convert(src.Palette[i]).(color.NRGBA)
}
parallel(dstH, func(partStart, partEnd int) {
for dstY := partStart; dstY < partEnd; dstY++ {
di := dst.PixOffset(0, dstY)
si := src.PixOffset(srcMinX, srcMinY+dstY)
for dstX := 0; dstX < dstW; dstX++ {
c := pnew[src.Pix[si]]
dst.Pix[di+0] = c.R
dst.Pix[di+1] = c.G
dst.Pix[di+2] = c.B
dst.Pix[di+3] = c.A
di += 4
si += 1
}
}
})
default:
parallel(dstH, func(partStart, partEnd int) {
for dstY := partStart; dstY < partEnd; dstY++ {
di := dst.PixOffset(0, dstY)
for dstX := 0; dstX < dstW; dstX++ {
c := color.NRGBAModel.Convert(img.At(srcMinX+dstX, srcMinY+dstY)).(color.NRGBA)
dst.Pix[di+0] = c.R
dst.Pix[di+1] = c.G
dst.Pix[di+2] = c.B
dst.Pix[di+3] = c.A
di += 4
}
}
})
}
return dst
}
// This function used internally to convert any image type to NRGBA if needed.
func toNRGBA(img image.Image) *image.NRGBA {
srcBounds := img.Bounds()
if srcBounds.Min.X == 0 && srcBounds.Min.Y == 0 {
if src0, ok := img.(*image.NRGBA); ok {
return src0
}
}
return Clone(img)
}

583
vendor/github.com/disintegration/imaging/resize.go generated vendored Normal file
View file

@ -0,0 +1,583 @@
package imaging
import (
"image"
"math"
)
type iwpair struct {
i int
w int32
}
type pweights struct {
iwpairs []iwpair
wsum int32
}
func precomputeWeights(dstSize, srcSize int, filter ResampleFilter) []pweights {
du := float64(srcSize) / float64(dstSize)
scale := du
if scale < 1.0 {
scale = 1.0
}
ru := math.Ceil(scale * filter.Support)
out := make([]pweights, dstSize)
for v := 0; v < dstSize; v++ {
fu := (float64(v)+0.5)*du - 0.5
startu := int(math.Ceil(fu - ru))
if startu < 0 {
startu = 0
}
endu := int(math.Floor(fu + ru))
if endu > srcSize-1 {
endu = srcSize - 1
}
wsum := int32(0)
for u := startu; u <= endu; u++ {
w := int32(0xff * filter.Kernel((float64(u)-fu)/scale))
if w != 0 {
wsum += w
out[v].iwpairs = append(out[v].iwpairs, iwpair{u, w})
}
}
out[v].wsum = wsum
}
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{}
}
src := toNRGBA(img)
srcW := src.Bounds().Max.X
srcH := src.Bounds().Max.Y
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)))
}
var dst *image.NRGBA
if filter.Support <= 0.0 {
// nearest-neighbor special case
dst = resizeNearest(src, dstW, dstH)
} else {
// two-pass resize
if srcW != dstW {
dst = resizeHorizontal(src, dstW, filter)
} else {
dst = src
}
if srcH != dstH {
dst = resizeVertical(dst, dstH, filter)
}
}
return dst
}
func resizeHorizontal(src *image.NRGBA, width int, filter ResampleFilter) *image.NRGBA {
srcBounds := src.Bounds()
srcW := srcBounds.Max.X
srcH := srcBounds.Max.Y
dstW := width
dstH := srcH
dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
weights := precomputeWeights(dstW, srcW, filter)
parallel(dstH, func(partStart, partEnd int) {
for dstY := partStart; dstY < partEnd; dstY++ {
for dstX := 0; dstX < dstW; dstX++ {
var c [4]int32
for _, iw := range weights[dstX].iwpairs {
i := dstY*src.Stride + iw.i*4
c[0] += int32(src.Pix[i+0]) * iw.w
c[1] += int32(src.Pix[i+1]) * iw.w
c[2] += int32(src.Pix[i+2]) * iw.w
c[3] += int32(src.Pix[i+3]) * iw.w
}
j := dstY*dst.Stride + dstX*4
sum := weights[dstX].wsum
dst.Pix[j+0] = clampint32(int32(float32(c[0])/float32(sum) + 0.5))
dst.Pix[j+1] = clampint32(int32(float32(c[1])/float32(sum) + 0.5))
dst.Pix[j+2] = clampint32(int32(float32(c[2])/float32(sum) + 0.5))
dst.Pix[j+3] = clampint32(int32(float32(c[3])/float32(sum) + 0.5))
}
}
})
return dst
}
func resizeVertical(src *image.NRGBA, height int, filter ResampleFilter) *image.NRGBA {
srcBounds := src.Bounds()
srcW := srcBounds.Max.X
srcH := srcBounds.Max.Y
dstW := srcW
dstH := height
dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
weights := precomputeWeights(dstH, srcH, filter)
parallel(dstW, func(partStart, partEnd int) {
for dstX := partStart; dstX < partEnd; dstX++ {
for dstY := 0; dstY < dstH; dstY++ {
var c [4]int32
for _, iw := range weights[dstY].iwpairs {
i := iw.i*src.Stride + dstX*4
c[0] += int32(src.Pix[i+0]) * iw.w
c[1] += int32(src.Pix[i+1]) * iw.w
c[2] += int32(src.Pix[i+2]) * iw.w
c[3] += int32(src.Pix[i+3]) * iw.w
}
j := dstY*dst.Stride + dstX*4
sum := weights[dstY].wsum
dst.Pix[j+0] = clampint32(int32(float32(c[0])/float32(sum) + 0.5))
dst.Pix[j+1] = clampint32(int32(float32(c[1])/float32(sum) + 0.5))
dst.Pix[j+2] = clampint32(int32(float32(c[2])/float32(sum) + 0.5))
dst.Pix[j+3] = clampint32(int32(float32(c[3])/float32(sum) + 0.5))
}
}
})
return dst
}
// fast nearest-neighbor resize, no filtering
func resizeNearest(src *image.NRGBA, width, height int) *image.NRGBA {
dstW, dstH := width, height
srcBounds := src.Bounds()
srcW := srcBounds.Max.X
srcH := srcBounds.Max.Y
dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
dx := float64(srcW) / float64(dstW)
dy := float64(srcH) / float64(dstH)
parallel(dstH, func(partStart, partEnd int) {
for dstY := partStart; dstY < partEnd; dstY++ {
fy := (float64(dstY)+0.5)*dy - 0.5
for dstX := 0; dstX < dstW; dstX++ {
fx := (float64(dstX)+0.5)*dx - 0.5
srcX := int(math.Min(math.Max(math.Floor(fx+0.5), 0.0), float64(srcW)))
srcY := int(math.Min(math.Max(math.Floor(fy+0.5), 0.0), float64(srcH)))
srcOff := srcY*src.Stride + srcX*4
dstOff := dstY*dst.Stride + dstX*4
copy(dst.Pix[dstOff:dstOff+4], src.Pix[srcOff:srcOff+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)
}
// Resample filter struct. It can be used to make custom filters.
//
// Supported resample filters: NearestNeighbor, Box, Linear, Hermite, MitchellNetravali,
// CatmullRom, BSpline, Gaussian, Lanczos, Hann, Hamming, Blackman, Bartlett, Welch, Cosine.
//
// General filter recommendations:
//
// - Lanczos
// Probably the best resampling filter for photographic images yielding sharp results,
// but 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 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 resampling filter appropriate for downscaling.
// When upscaling it's similar to NearestNeighbor.
//
// - NearestNeighbor
// Fastest resample filter, no antialiasing at all. Rarely used.
//
type ResampleFilter struct {
Support float64
Kernel func(float64) float64
}
// 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
// Mitchell-Netravali cubic filter (BC-spline; B=1/3; C=1/3).
var MitchellNetravali ResampleFilter
// Catmull-Rom - sharp cubic filter (BC-spline; B=0; C=0.5).
var CatmullRom ResampleFilter
// Cubic B-spline - smooth cubic filter (BC-spline; B=1; C=0).
var BSpline ResampleFilter
// Gaussian Blurring Filter.
var Gaussian ResampleFilter
// Bartlett-windowed sinc filter (3 lobes).
var Bartlett ResampleFilter
// Lanczos filter (3 lobes).
var Lanczos ResampleFilter
// Hann-windowed sinc filter (3 lobes).
var Hann ResampleFilter
// Hamming-windowed sinc filter (3 lobes).
var Hamming ResampleFilter
// Blackman-windowed sinc filter (3 lobes).
var Blackman ResampleFilter
// Welch-windowed sinc filter (parabolic window, 3 lobes).
var Welch ResampleFilter
// Cosine-windowed sinc filter (3 lobes).
var Cosine ResampleFilter
func bcspline(x, b, c float64) float64 {
x = math.Abs(x)
if x < 1.0 {
return ((12-9*b-6*c)*x*x*x + (-18+12*b+6*c)*x*x + (6 - 2*b)) / 6
}
if x < 2.0 {
return ((-b-6*c)*x*x*x + (6*b+30*c)*x*x + (-12*b-48*c)*x + (8*b + 24*c)) / 6
}
return 0
}
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
},
}
}

182
vendor/github.com/disintegration/imaging/tools.go generated vendored Normal file
View file

@ -0,0 +1,182 @@
package imaging
import (
"image"
"math"
)
// Anchor is the anchor point for image alignment.
type Anchor int
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 {
src := toNRGBA(img)
srcRect := rect.Sub(img.Bounds().Min)
sub := src.SubImage(srcRect)
return Clone(sub) // New image Bounds().Min point will be (0, 0)
}
// 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 {
src := toNRGBA(img)
dst := Clone(background) // cloned image bounds start at (0, 0)
startPt := pos.Sub(background.Bounds().Min) // so we should translate start point
endPt := startPt.Add(src.Bounds().Size())
pasteBounds := image.Rectangle{startPt, endPt}
if dst.Bounds().Overlaps(pasteBounds) {
intersectBounds := dst.Bounds().Intersect(pasteBounds)
rowSize := intersectBounds.Dx() * 4
numRows := intersectBounds.Dy()
srcStartX := intersectBounds.Min.X - pasteBounds.Min.X
srcStartY := intersectBounds.Min.Y - pasteBounds.Min.Y
i0 := dst.PixOffset(intersectBounds.Min.X, intersectBounds.Min.Y)
j0 := src.PixOffset(srcStartX, srcStartY)
di := dst.Stride
dj := src.Stride
for row := 0; row < numRows; row++ {
copy(dst.Pix[i0:i0+rowSize], src.Pix[j0:j0+rowSize])
i0 += di
j0 += dj
}
}
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 the sprite over the background at position (50, 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) // check: 0.0 <= opacity <= 1.0
src := toNRGBA(img)
dst := Clone(background) // cloned image bounds start at (0, 0)
startPt := pos.Sub(background.Bounds().Min) // so we should translate start point
endPt := startPt.Add(src.Bounds().Size())
pasteBounds := image.Rectangle{startPt, endPt}
if dst.Bounds().Overlaps(pasteBounds) {
intersectBounds := dst.Bounds().Intersect(pasteBounds)
for y := intersectBounds.Min.Y; y < intersectBounds.Max.Y; y++ {
for x := intersectBounds.Min.X; x < intersectBounds.Max.X; x++ {
i := y*dst.Stride + x*4
srcX := x - pasteBounds.Min.X
srcY := y - pasteBounds.Min.Y
j := srcY*src.Stride + srcX*4
a1 := float64(dst.Pix[i+3])
a2 := float64(src.Pix[j+3])
coef2 := opacity * a2 / 255.0
coef1 := (1 - coef2) * a1 / 255.0
coefSum := coef1 + coef2
coef1 /= coefSum
coef2 /= coefSum
dst.Pix[i+0] = uint8(float64(dst.Pix[i+0])*coef1 + float64(src.Pix[j+0])*coef2)
dst.Pix[i+1] = uint8(float64(dst.Pix[i+1])*coef1 + float64(src.Pix[j+1])*coef2)
dst.Pix[i+2] = uint8(float64(dst.Pix[i+2])*coef1 + float64(src.Pix[j+2])*coef2)
dst.Pix[i+3] = uint8(math.Min(a1+a2*opacity*(255.0-a1)/255.0, 255.0))
}
}
}
return dst
}

201
vendor/github.com/disintegration/imaging/transform.go generated vendored Normal file
View file

@ -0,0 +1,201 @@
package imaging
import (
"image"
)
// Rotate90 rotates the image 90 degrees counterclockwise and returns the transformed image.
func Rotate90(img image.Image) *image.NRGBA {
src := toNRGBA(img)
srcW := src.Bounds().Max.X
srcH := src.Bounds().Max.Y
dstW := srcH
dstH := srcW
dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
parallel(dstH, func(partStart, partEnd int) {
for dstY := partStart; dstY < partEnd; dstY++ {
for dstX := 0; dstX < dstW; dstX++ {
srcX := dstH - dstY - 1
srcY := dstX
srcOff := srcY*src.Stride + srcX*4
dstOff := dstY*dst.Stride + dstX*4
copy(dst.Pix[dstOff:dstOff+4], src.Pix[srcOff:srcOff+4])
}
}
})
return dst
}
// Rotate180 rotates the image 180 degrees counterclockwise and returns the transformed image.
func Rotate180(img image.Image) *image.NRGBA {
src := toNRGBA(img)
srcW := src.Bounds().Max.X
srcH := src.Bounds().Max.Y
dstW := srcW
dstH := srcH
dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
parallel(dstH, func(partStart, partEnd int) {
for dstY := partStart; dstY < partEnd; dstY++ {
for dstX := 0; dstX < dstW; dstX++ {
srcX := dstW - dstX - 1
srcY := dstH - dstY - 1
srcOff := srcY*src.Stride + srcX*4
dstOff := dstY*dst.Stride + dstX*4
copy(dst.Pix[dstOff:dstOff+4], src.Pix[srcOff:srcOff+4])
}
}
})
return dst
}
// Rotate270 rotates the image 270 degrees counterclockwise and returns the transformed image.
func Rotate270(img image.Image) *image.NRGBA {
src := toNRGBA(img)
srcW := src.Bounds().Max.X
srcH := src.Bounds().Max.Y
dstW := srcH
dstH := srcW
dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
parallel(dstH, func(partStart, partEnd int) {
for dstY := partStart; dstY < partEnd; dstY++ {
for dstX := 0; dstX < dstW; dstX++ {
srcX := dstY
srcY := dstW - dstX - 1
srcOff := srcY*src.Stride + srcX*4
dstOff := dstY*dst.Stride + dstX*4
copy(dst.Pix[dstOff:dstOff+4], src.Pix[srcOff:srcOff+4])
}
}
})
return dst
}
// FlipH flips the image horizontally (from left to right) and returns the transformed image.
func FlipH(img image.Image) *image.NRGBA {
src := toNRGBA(img)
srcW := src.Bounds().Max.X
srcH := src.Bounds().Max.Y
dstW := srcW
dstH := srcH
dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
parallel(dstH, func(partStart, partEnd int) {
for dstY := partStart; dstY < partEnd; dstY++ {
for dstX := 0; dstX < dstW; dstX++ {
srcX := dstW - dstX - 1
srcY := dstY
srcOff := srcY*src.Stride + srcX*4
dstOff := dstY*dst.Stride + dstX*4
copy(dst.Pix[dstOff:dstOff+4], src.Pix[srcOff:srcOff+4])
}
}
})
return dst
}
// FlipV flips the image vertically (from top to bottom) and returns the transformed image.
func FlipV(img image.Image) *image.NRGBA {
src := toNRGBA(img)
srcW := src.Bounds().Max.X
srcH := src.Bounds().Max.Y
dstW := srcW
dstH := srcH
dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
parallel(dstH, func(partStart, partEnd int) {
for dstY := partStart; dstY < partEnd; dstY++ {
for dstX := 0; dstX < dstW; dstX++ {
srcX := dstX
srcY := dstH - dstY - 1
srcOff := srcY*src.Stride + srcX*4
dstOff := dstY*dst.Stride + dstX*4
copy(dst.Pix[dstOff:dstOff+4], src.Pix[srcOff:srcOff+4])
}
}
})
return dst
}
// Transpose flips the image horizontally and rotates 90 degrees counter-clockwise.
func Transpose(img image.Image) *image.NRGBA {
src := toNRGBA(img)
srcW := src.Bounds().Max.X
srcH := src.Bounds().Max.Y
dstW := srcH
dstH := srcW
dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
parallel(dstH, func(partStart, partEnd int) {
for dstY := partStart; dstY < partEnd; dstY++ {
for dstX := 0; dstX < dstW; dstX++ {
srcX := dstY
srcY := dstX
srcOff := srcY*src.Stride + srcX*4
dstOff := dstY*dst.Stride + dstX*4
copy(dst.Pix[dstOff:dstOff+4], src.Pix[srcOff:srcOff+4])
}
}
})
return dst
}
// Transverse flips the image vertically and rotates 90 degrees counter-clockwise.
func Transverse(img image.Image) *image.NRGBA {
src := toNRGBA(img)
srcW := src.Bounds().Max.X
srcH := src.Bounds().Max.Y
dstW := srcH
dstH := srcW
dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
parallel(dstH, func(partStart, partEnd int) {
for dstY := partStart; dstY < partEnd; dstY++ {
for dstX := 0; dstX < dstW; dstX++ {
srcX := dstH - dstY - 1
srcY := dstW - dstX - 1
srcOff := srcY*src.Stride + srcX*4
dstOff := dstY*dst.Stride + dstX*4
copy(dst.Pix[dstOff:dstOff+4], src.Pix[srcOff:srcOff+4])
}
}
})
return dst
}

77
vendor/github.com/disintegration/imaging/utils.go generated vendored Normal file
View file

@ -0,0 +1,77 @@
package imaging
import (
"math"
"runtime"
"sync"
"sync/atomic"
)
var parallelizationEnabled = true
// if GOMAXPROCS = 1: no goroutines used
// if GOMAXPROCS > 1: spawn N=GOMAXPROCS workers in separate goroutines
func parallel(dataSize int, fn func(partStart, partEnd int)) {
numGoroutines := 1
partSize := dataSize
if parallelizationEnabled {
numProcs := runtime.GOMAXPROCS(0)
if numProcs > 1 {
numGoroutines = numProcs
partSize = dataSize / (numGoroutines * 10)
if partSize < 1 {
partSize = 1
}
}
}
if numGoroutines == 1 {
fn(0, dataSize)
} else {
var wg sync.WaitGroup
wg.Add(numGoroutines)
idx := uint64(0)
for p := 0; p < numGoroutines; p++ {
go func() {
defer wg.Done()
for {
partStart := int(atomic.AddUint64(&idx, uint64(partSize))) - partSize
if partStart >= dataSize {
break
}
partEnd := partStart + partSize
if partEnd > dataSize {
partEnd = dataSize
}
fn(partStart, partEnd)
}
}()
}
wg.Wait()
}
}
func absint(i int) int {
if i < 0 {
return -i
}
return i
}
// clamp & round float64 to uint8 (0..255)
func clamp(v float64) uint8 {
return uint8(math.Min(math.Max(v, 0.0), 255.0) + 0.5)
}
// clamp int32 to uint8 (0..255)
func clampint32(v int32) uint8 {
if v < 0 {
return 0
} else if v > 255 {
return 255
}
return uint8(v)
}

191
vendor/github.com/golang/glog/LICENSE generated vendored Normal file
View file

@ -0,0 +1,191 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, "control" means (i) the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity exercising
permissions granted by this License.
"Source" form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
"Object" form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
"submitted" means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
2. Grant of Copyright License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
3. Grant of Patent License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.
4. Redistribution.
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
You must give any other recipients of the Work or Derivative Works a copy of
this License; and
You must cause any modified files to carry prominent notices stating that You
changed the files; and
You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
If the Work includes a "NOTICE" text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
5. Submission of Contributions.
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
6. Trademarks.
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
7. Disclaimer of Warranty.
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
8. Limitation of Liability.
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability.
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets "[]" replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included on
the same "printed page" as the copyright notice for easier identification within
third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

44
vendor/github.com/golang/glog/README generated vendored Normal file
View file

@ -0,0 +1,44 @@
glog
====
Leveled execution logs for Go.
This is an efficient pure Go implementation of leveled logs in the
manner of the open source C++ package
http://code.google.com/p/google-glog
By binding methods to booleans it is possible to use the log package
without paying the expense of evaluating the arguments to the log.
Through the -vmodule flag, the package also provides fine-grained
control over logging at the file level.
The comment from glog.go introduces the ideas:
Package glog implements logging analogous to the Google-internal
C++ INFO/ERROR/V setup. It provides functions Info, Warning,
Error, Fatal, plus formatting variants such as Infof. It
also provides V-style logging controlled by the -v and
-vmodule=file=2 flags.
Basic examples:
glog.Info("Prepare to repel boarders")
glog.Fatalf("Initialization failed: %s", err)
See the documentation for the V function for an explanation
of these examples:
if glog.V(2) {
glog.Info("Starting transaction...")
}
glog.V(2).Infoln("Processed", nItems, "elements")
The repository contains an open source version of the log package
used inside Google. The master copy of the source lives inside
Google, not here. The code in this repo is for export only and is not itself
under development. Feature requests will be ignored.
Send bug reports to golang-nuts@googlegroups.com.

1180
vendor/github.com/golang/glog/glog.go generated vendored Normal file

File diff suppressed because it is too large Load diff

124
vendor/github.com/golang/glog/glog_file.go generated vendored Normal file
View file

@ -0,0 +1,124 @@
// Go support for leveled logs, analogous to https://code.google.com/p/google-glog/
//
// Copyright 2013 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// File I/O for logs.
package glog
import (
"errors"
"flag"
"fmt"
"os"
"os/user"
"path/filepath"
"strings"
"sync"
"time"
)
// MaxSize is the maximum size of a log file in bytes.
var MaxSize uint64 = 1024 * 1024 * 1800
// logDirs lists the candidate directories for new log files.
var logDirs []string
// If non-empty, overrides the choice of directory in which to write logs.
// See createLogDirs for the full list of possible destinations.
var logDir = flag.String("log_dir", "", "If non-empty, write log files in this directory")
func createLogDirs() {
if *logDir != "" {
logDirs = append(logDirs, *logDir)
}
logDirs = append(logDirs, os.TempDir())
}
var (
pid = os.Getpid()
program = filepath.Base(os.Args[0])
host = "unknownhost"
userName = "unknownuser"
)
func init() {
h, err := os.Hostname()
if err == nil {
host = shortHostname(h)
}
current, err := user.Current()
if err == nil {
userName = current.Username
}
// Sanitize userName since it may contain filepath separators on Windows.
userName = strings.Replace(userName, `\`, "_", -1)
}
// shortHostname returns its argument, truncating at the first period.
// For instance, given "www.google.com" it returns "www".
func shortHostname(hostname string) string {
if i := strings.Index(hostname, "."); i >= 0 {
return hostname[:i]
}
return hostname
}
// logName returns a new log file name containing tag, with start time t, and
// the name for the symlink for tag.
func logName(tag string, t time.Time) (name, link string) {
name = fmt.Sprintf("%s.%s.%s.log.%s.%04d%02d%02d-%02d%02d%02d.%d",
program,
host,
userName,
tag,
t.Year(),
t.Month(),
t.Day(),
t.Hour(),
t.Minute(),
t.Second(),
pid)
return name, program + "." + tag
}
var onceLogDirs sync.Once
// create creates a new log file and returns the file and its filename, which
// contains tag ("INFO", "FATAL", etc.) and t. If the file is created
// successfully, create also attempts to update the symlink for that tag, ignoring
// errors.
func create(tag string, t time.Time) (f *os.File, filename string, err error) {
onceLogDirs.Do(createLogDirs)
if len(logDirs) == 0 {
return nil, "", errors.New("log: no log dirs")
}
name, link := logName(tag, t)
var lastErr error
for _, dir := range logDirs {
fname := filepath.Join(dir, name)
f, err := os.Create(fname)
if err == nil {
symlink := filepath.Join(dir, link)
os.Remove(symlink) // ignore err
os.Symlink(name, symlink) // ignore err
return f, fname, nil
}
lastErr = err
}
return nil, "", fmt.Errorf("log: cannot create log: %v", lastErr)
}

7
vendor/github.com/gregjones/httpcache/LICENSE.txt generated vendored Normal file
View file

@ -0,0 +1,7 @@
Copyright © 2012 Greg Jones (greg.jones@gmail.com)
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.

21
vendor/github.com/gregjones/httpcache/README.md generated vendored Normal file
View file

@ -0,0 +1,21 @@
httpcache
=========
A Transport for Go's http.Client that will cache responses according to the HTTP RFC
Package httpcache provides a http.RoundTripper implementation that works as a mostly RFC-compliant cache for http responses.
It is only suitable for use as a 'private' cache (i.e. for a web-browser or an API-client and not for a shared proxy).
**Documentation:** http://godoc.org/github.com/gregjones/httpcache
**License:** MIT (see LICENSE.txt)
Cache backends
--------------
- The built-in 'memory' cache stores responses in an in-memory map.
- https://github.com/gregjones/httpcache/diskcache provides a filesystem-backed cache using the [diskv](https://github.com/peterbourgon/diskv) library.
- https://github.com/gregjones/httpcache/memcache provides memcache implementations, for both App Engine and 'normal' memcache servers
- https://github.com/sourcegraph/s3cache uses Amazon S3 for storage.
- https://github.com/gregjones/httpcache/leveldbcache provides a filesystem-backed cache using [leveldb](https://github.com/syndtr/goleveldb/leveldb)

View file

@ -0,0 +1,61 @@
// Package diskcache provides an implementation of httpcache.Cache that uses the diskv package
// to supplement an in-memory map with persistent storage
//
package diskcache
import (
"bytes"
"crypto/md5"
"encoding/hex"
"github.com/peterbourgon/diskv"
"io"
)
// Cache is an implementation of httpcache.Cache that supplements the in-memory map with persistent storage
type Cache struct {
d *diskv.Diskv
}
// Get returns the response corresponding to key if present
func (c *Cache) Get(key string) (resp []byte, ok bool) {
key = keyToFilename(key)
resp, err := c.d.Read(key)
if err != nil {
return []byte{}, false
}
return resp, true
}
// Set saves a response to the cache as key
func (c *Cache) Set(key string, resp []byte) {
key = keyToFilename(key)
c.d.WriteStream(key, bytes.NewReader(resp), true)
}
// Delete removes the response with key from the cache
func (c *Cache) Delete(key string) {
key = keyToFilename(key)
c.d.Erase(key)
}
func keyToFilename(key string) string {
h := md5.New()
io.WriteString(h, key)
return hex.EncodeToString(h.Sum(nil))
}
// New returns a new Cache that will store files in basePath
func New(basePath string) *Cache {
return &Cache{
d: diskv.New(diskv.Options{
BasePath: basePath,
CacheSizeMax: 100 * 1024 * 1024, // 100MB
}),
}
}
// NewWithDiskv returns a new Cache using the provided Diskv as underlying
// storage.
func NewWithDiskv(d *diskv.Diskv) *Cache {
return &Cache{d}
}

597
vendor/github.com/gregjones/httpcache/httpcache.go generated vendored Normal file
View file

@ -0,0 +1,597 @@
// Package httpcache provides a http.RoundTripper implementation that works as a
// mostly RFC-compliant cache for http responses.
//
// It is only suitable for use as a 'private' cache (i.e. for a web-browser or an API-client
// and not for a shared proxy).
//
package httpcache
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"log"
"net/http"
"net/http/httputil"
"strings"
"sync"
"time"
)
const (
stale = iota
fresh
transparent
// XFromCache is the header added to responses that are returned from the cache
XFromCache = "X-From-Cache"
)
// A Cache interface is used by the Transport to store and retrieve responses.
type Cache interface {
// Get returns the []byte representation of a cached response and a bool
// set to true if the value isn't empty
Get(key string) (responseBytes []byte, ok bool)
// Set stores the []byte representation of a response against a key
Set(key string, responseBytes []byte)
// Delete removes the value associated with the key
Delete(key string)
}
// cacheKey returns the cache key for req.
func cacheKey(req *http.Request) string {
return req.URL.String()
}
// CachedResponse returns the cached http.Response for req if present, and nil
// otherwise.
func CachedResponse(c Cache, req *http.Request) (resp *http.Response, err error) {
cachedVal, ok := c.Get(cacheKey(req))
if !ok {
return
}
b := bytes.NewBuffer(cachedVal)
return http.ReadResponse(bufio.NewReader(b), req)
}
// MemoryCache is an implemtation of Cache that stores responses in an in-memory map.
type MemoryCache struct {
mu sync.RWMutex
items map[string][]byte
}
// Get returns the []byte representation of the response and true if present, false if not
func (c *MemoryCache) Get(key string) (resp []byte, ok bool) {
c.mu.RLock()
defer c.mu.RUnlock()
resp, ok = c.items[key]
return resp, ok
}
// Set saves response resp to the cache with key
func (c *MemoryCache) Set(key string, resp []byte) {
c.mu.Lock()
defer c.mu.Unlock()
c.items[key] = resp
}
// Delete removes key from the cache
func (c *MemoryCache) Delete(key string) {
c.mu.Lock()
defer c.mu.Unlock()
delete(c.items, key)
}
// NewMemoryCache returns a new Cache that will store items in an in-memory map
func NewMemoryCache() *MemoryCache {
c := &MemoryCache{items: map[string][]byte{}}
return c
}
// onEOFReader executes a function on reader EOF or close
type onEOFReader struct {
rc io.ReadCloser
fn func()
}
func (r *onEOFReader) Read(p []byte) (n int, err error) {
n, err = r.rc.Read(p)
if err == io.EOF {
r.runFunc()
}
return
}
func (r *onEOFReader) Close() error {
err := r.rc.Close()
r.runFunc()
return err
}
func (r *onEOFReader) runFunc() {
if fn := r.fn; fn != nil {
fn()
r.fn = nil
}
}
// Transport is an implementation of http.RoundTripper that will return values from a cache
// where possible (avoiding a network request) and will additionally add validators (etag/if-modified-since)
// to repeated requests allowing servers to return 304 / Not Modified
type Transport struct {
// The RoundTripper interface actually used to make requests
// If nil, http.DefaultTransport is used
Transport http.RoundTripper
Cache Cache
// If true, responses returned from the cache will be given an extra header, X-From-Cache
MarkCachedResponses bool
// guards modReq
mu sync.RWMutex
// Mapping of original request => cloned
modReq map[*http.Request]*http.Request
}
// NewTransport returns a new Transport with the
// provided Cache implementation and MarkCachedResponses set to true
func NewTransport(c Cache) *Transport {
return &Transport{Cache: c, MarkCachedResponses: true}
}
// Client returns an *http.Client that caches responses.
func (t *Transport) Client() *http.Client {
return &http.Client{Transport: t}
}
// varyMatches will return false unless all of the cached values for the headers listed in Vary
// match the new request
func varyMatches(cachedResp *http.Response, req *http.Request) bool {
for _, header := range headerAllCommaSepValues(cachedResp.Header, "vary") {
header = http.CanonicalHeaderKey(header)
if header != "" && req.Header.Get(header) != cachedResp.Header.Get("X-Varied-"+header) {
return false
}
}
return true
}
// setModReq maintains a mapping between original requests and their associated cloned requests
func (t *Transport) setModReq(orig, mod *http.Request) {
t.mu.Lock()
defer t.mu.Unlock()
if t.modReq == nil {
t.modReq = make(map[*http.Request]*http.Request)
}
if mod == nil {
delete(t.modReq, orig)
} else {
t.modReq[orig] = mod
}
}
// RoundTrip takes a Request and returns a Response
//
// If there is a fresh Response already in cache, then it will be returned without connecting to
// the server.
//
// If there is a stale Response, then any validators it contains will be set on the new request
// to give the server a chance to respond with NotModified. If this happens, then the cached Response
// will be returned.
func (t *Transport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
cacheKey := cacheKey(req)
cacheableMethod := req.Method == "GET" || req.Method == "HEAD"
var cachedResp *http.Response
if cacheableMethod {
cachedResp, err = CachedResponse(t.Cache, req)
} else {
// Need to invalidate an existing value
t.Cache.Delete(cacheKey)
}
transport := t.Transport
if transport == nil {
transport = http.DefaultTransport
}
if cachedResp != nil && err == nil && cacheableMethod && req.Header.Get("range") == "" {
if t.MarkCachedResponses {
cachedResp.Header.Set(XFromCache, "1")
}
if varyMatches(cachedResp, req) {
// Can only use cached value if the new request doesn't Vary significantly
freshness := getFreshness(cachedResp.Header, req.Header)
if freshness == fresh {
return cachedResp, nil
}
if freshness == stale {
var req2 *http.Request
// Add validators if caller hasn't already done so
etag := cachedResp.Header.Get("etag")
if etag != "" && req.Header.Get("etag") == "" {
req2 = cloneRequest(req)
req2.Header.Set("if-none-match", etag)
}
lastModified := cachedResp.Header.Get("last-modified")
if lastModified != "" && req.Header.Get("last-modified") == "" {
if req2 == nil {
req2 = cloneRequest(req)
}
req2.Header.Set("if-modified-since", lastModified)
}
if req2 != nil {
// Associate original request with cloned request so we can refer to
// it in CancelRequest()
t.setModReq(req, req2)
req = req2
defer func() {
// Release req/clone mapping on error
if err != nil {
t.setModReq(req, nil)
}
if resp != nil {
// Release req/clone mapping on body close/EOF
resp.Body = &onEOFReader{
rc: resp.Body,
fn: func() { t.setModReq(req, nil) },
}
}
}()
}
}
}
resp, err = transport.RoundTrip(req)
if err == nil && req.Method == "GET" && resp.StatusCode == http.StatusNotModified {
// Replace the 304 response with the one from cache, but update with some new headers
endToEndHeaders := getEndToEndHeaders(resp.Header)
for _, header := range endToEndHeaders {
cachedResp.Header[header] = resp.Header[header]
}
cachedResp.Status = fmt.Sprintf("%d %s", http.StatusOK, http.StatusText(http.StatusOK))
cachedResp.StatusCode = http.StatusOK
resp = cachedResp
} else if (err != nil || (cachedResp != nil && resp.StatusCode >= 500)) &&
req.Method == "GET" && canStaleOnError(cachedResp.Header, req.Header) {
// In case of transport failure and stale-if-error activated, returns cached content
// when available
cachedResp.Status = fmt.Sprintf("%d %s", http.StatusOK, http.StatusText(http.StatusOK))
cachedResp.StatusCode = http.StatusOK
return cachedResp, nil
} else {
if err != nil || resp.StatusCode != http.StatusOK {
t.Cache.Delete(cacheKey)
}
if err != nil {
return nil, err
}
}
} else {
reqCacheControl := parseCacheControl(req.Header)
if _, ok := reqCacheControl["only-if-cached"]; ok {
resp = newGatewayTimeoutResponse(req)
} else {
resp, err = transport.RoundTrip(req)
if err != nil {
return nil, err
}
}
}
reqCacheControl := parseCacheControl(req.Header)
respCacheControl := parseCacheControl(resp.Header)
if canStore(reqCacheControl, respCacheControl) {
for _, varyKey := range headerAllCommaSepValues(resp.Header, "vary") {
varyKey = http.CanonicalHeaderKey(varyKey)
fakeHeader := "X-Varied-" + varyKey
reqValue := req.Header.Get(varyKey)
if reqValue != "" {
resp.Header.Set(fakeHeader, reqValue)
}
}
respBytes, err := httputil.DumpResponse(resp, true)
if err == nil {
t.Cache.Set(cacheKey, respBytes)
}
} else {
t.Cache.Delete(cacheKey)
}
return resp, nil
}
// CancelRequest calls CancelRequest on the underlaying transport if implemented or
// throw a warning otherwise.
func (t *Transport) CancelRequest(req *http.Request) {
type canceler interface {
CancelRequest(*http.Request)
}
tr, ok := t.Transport.(canceler)
if !ok {
log.Printf("httpcache: Client Transport of type %T doesn't support CancelRequest; Timeout not supported", t.Transport)
return
}
t.mu.RLock()
if modReq, ok := t.modReq[req]; ok {
t.mu.RUnlock()
t.mu.Lock()
delete(t.modReq, req)
t.mu.Unlock()
tr.CancelRequest(modReq)
} else {
t.mu.RUnlock()
tr.CancelRequest(req)
}
}
// ErrNoDateHeader indicates that the HTTP headers contained no Date header.
var ErrNoDateHeader = errors.New("no Date header")
// Date parses and returns the value of the Date header.
func Date(respHeaders http.Header) (date time.Time, err error) {
dateHeader := respHeaders.Get("date")
if dateHeader == "" {
err = ErrNoDateHeader
return
}
return time.Parse(time.RFC1123, dateHeader)
}
type realClock struct{}
func (c *realClock) since(d time.Time) time.Duration {
return time.Since(d)
}
type timer interface {
since(d time.Time) time.Duration
}
var clock timer = &realClock{}
// getFreshness will return one of fresh/stale/transparent based on the cache-control
// values of the request and the response
//
// fresh indicates the response can be returned
// stale indicates that the response needs validating before it is returned
// transparent indicates the response should not be used to fulfil the request
//
// Because this is only a private cache, 'public' and 'private' in cache-control aren't
// signficant. Similarly, smax-age isn't used.
func getFreshness(respHeaders, reqHeaders http.Header) (freshness int) {
respCacheControl := parseCacheControl(respHeaders)
reqCacheControl := parseCacheControl(reqHeaders)
if _, ok := reqCacheControl["no-cache"]; ok {
return transparent
}
if _, ok := respCacheControl["no-cache"]; ok {
return stale
}
if _, ok := reqCacheControl["only-if-cached"]; ok {
return fresh
}
date, err := Date(respHeaders)
if err != nil {
return stale
}
currentAge := clock.since(date)
var lifetime time.Duration
var zeroDuration time.Duration
// If a response includes both an Expires header and a max-age directive,
// the max-age directive overrides the Expires header, even if the Expires header is more restrictive.
if maxAge, ok := respCacheControl["max-age"]; ok {
lifetime, err = time.ParseDuration(maxAge + "s")
if err != nil {
lifetime = zeroDuration
}
} else {
expiresHeader := respHeaders.Get("Expires")
if expiresHeader != "" {
expires, err := time.Parse(time.RFC1123, expiresHeader)
if err != nil {
lifetime = zeroDuration
} else {
lifetime = expires.Sub(date)
}
}
}
if maxAge, ok := reqCacheControl["max-age"]; ok {
// the client is willing to accept a response whose age is no greater than the specified time in seconds
lifetime, err = time.ParseDuration(maxAge + "s")
if err != nil {
lifetime = zeroDuration
}
}
if minfresh, ok := reqCacheControl["min-fresh"]; ok {
// the client wants a response that will still be fresh for at least the specified number of seconds.
minfreshDuration, err := time.ParseDuration(minfresh + "s")
if err == nil {
currentAge = time.Duration(currentAge + minfreshDuration)
}
}
if maxstale, ok := reqCacheControl["max-stale"]; ok {
// Indicates that the client is willing to accept a response that has exceeded its expiration time.
// If max-stale is assigned a value, then the client is willing to accept a response that has exceeded
// its expiration time by no more than the specified number of seconds.
// If no value is assigned to max-stale, then the client is willing to accept a stale response of any age.
//
// Responses served only because of a max-stale value are supposed to have a Warning header added to them,
// but that seems like a hassle, and is it actually useful? If so, then there needs to be a different
// return-value available here.
if maxstale == "" {
return fresh
}
maxstaleDuration, err := time.ParseDuration(maxstale + "s")
if err == nil {
currentAge = time.Duration(currentAge - maxstaleDuration)
}
}
if lifetime > currentAge {
return fresh
}
return stale
}
// Returns true if either the request or the response includes the stale-if-error
// cache control extension: https://tools.ietf.org/html/rfc5861
func canStaleOnError(respHeaders, reqHeaders http.Header) bool {
respCacheControl := parseCacheControl(respHeaders)
reqCacheControl := parseCacheControl(reqHeaders)
var err error
lifetime := time.Duration(-1)
if staleMaxAge, ok := respCacheControl["stale-if-error"]; ok {
if staleMaxAge != "" {
lifetime, err = time.ParseDuration(staleMaxAge + "s")
if err != nil {
return false
}
} else {
return true
}
}
if staleMaxAge, ok := reqCacheControl["stale-if-error"]; ok {
if staleMaxAge != "" {
lifetime, err = time.ParseDuration(staleMaxAge + "s")
if err != nil {
return false
}
} else {
return true
}
}
if lifetime >= 0 {
date, err := Date(respHeaders)
if err != nil {
return false
}
currentAge := clock.since(date)
if lifetime > currentAge {
return true
}
}
return false
}
func getEndToEndHeaders(respHeaders http.Header) []string {
// These headers are always hop-by-hop
hopByHopHeaders := map[string]struct{}{
"Connection": struct{}{},
"Keep-Alive": struct{}{},
"Proxy-Authenticate": struct{}{},
"Proxy-Authorization": struct{}{},
"Te": struct{}{},
"Trailers": struct{}{},
"Transfer-Encoding": struct{}{},
"Upgrade": struct{}{},
}
for _, extra := range strings.Split(respHeaders.Get("connection"), ",") {
// any header listed in connection, if present, is also considered hop-by-hop
if strings.Trim(extra, " ") != "" {
hopByHopHeaders[http.CanonicalHeaderKey(extra)] = struct{}{}
}
}
endToEndHeaders := []string{}
for respHeader, _ := range respHeaders {
if _, ok := hopByHopHeaders[respHeader]; !ok {
endToEndHeaders = append(endToEndHeaders, respHeader)
}
}
return endToEndHeaders
}
func canStore(reqCacheControl, respCacheControl cacheControl) (canStore bool) {
if _, ok := respCacheControl["no-store"]; ok {
return false
}
if _, ok := reqCacheControl["no-store"]; ok {
return false
}
return true
}
func newGatewayTimeoutResponse(req *http.Request) *http.Response {
var braw bytes.Buffer
braw.WriteString("HTTP/1.1 504 Gateway Timeout\r\n\r\n")
resp, err := http.ReadResponse(bufio.NewReader(&braw), req)
if err != nil {
panic(err)
}
return resp
}
// cloneRequest returns a clone of the provided *http.Request.
// The clone is a shallow copy of the struct and its Header map.
// (This function copyright goauth2 authors: https://code.google.com/p/goauth2)
func cloneRequest(r *http.Request) *http.Request {
// shallow copy of the struct
r2 := new(http.Request)
*r2 = *r
// deep copy of the Header
r2.Header = make(http.Header)
for k, s := range r.Header {
r2.Header[k] = s
}
return r2
}
type cacheControl map[string]string
func parseCacheControl(headers http.Header) cacheControl {
cc := cacheControl{}
ccHeader := headers.Get("Cache-Control")
for _, part := range strings.Split(ccHeader, ",") {
part = strings.Trim(part, " ")
if part == "" {
continue
}
if strings.ContainsRune(part, '=') {
keyval := strings.Split(part, "=")
cc[strings.Trim(keyval[0], " ")] = strings.Trim(keyval[1], ",")
} else {
cc[part] = ""
}
}
return cc
}
// headerAllCommaSepValues returns all comma-separated values (each
// with whitespace trimmed) for header name in headers. According to
// Section 4.2 of the HTTP/1.1 spec
// (http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2),
// values from multiple occurrences of a header should be concatenated, if
// the header's value is a comma-separated list.
func headerAllCommaSepValues(headers http.Header, name string) []string {
var vals []string
for _, val := range headers[http.CanonicalHeaderKey(name)] {
fields := strings.Split(val, ",")
for i, f := range fields {
fields[i] = strings.TrimSpace(f)
}
vals = append(vals, fields...)
}
return vals
}
// NewMemoryCacheTransport returns a new Transport using the in-memory cache implementation
func NewMemoryCacheTransport() *Transport {
c := NewMemoryCache()
t := NewTransport(c)
return t
}

39
vendor/github.com/petar/GoLLRB/llrb/avgvar.go generated vendored Normal file
View file

@ -0,0 +1,39 @@
// Copyright 2010 Petar Maymounkov. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package llrb
import "math"
// avgVar maintains the average and variance of a stream of numbers
// in a space-efficient manner.
type avgVar struct {
count int64
sum, sumsq float64
}
func (av *avgVar) Init() {
av.count = 0
av.sum = 0.0
av.sumsq = 0.0
}
func (av *avgVar) Add(sample float64) {
av.count++
av.sum += sample
av.sumsq += sample * sample
}
func (av *avgVar) GetCount() int64 { return av.count }
func (av *avgVar) GetAvg() float64 { return av.sum / float64(av.count) }
func (av *avgVar) GetTotal() float64 { return av.sum }
func (av *avgVar) GetVar() float64 {
a := av.GetAvg()
return av.sumsq/float64(av.count) - a*a
}
func (av *avgVar) GetStdDev() float64 { return math.Sqrt(av.GetVar()) }

93
vendor/github.com/petar/GoLLRB/llrb/iterator.go generated vendored Normal file
View file

@ -0,0 +1,93 @@
package llrb
type ItemIterator func(i Item) bool
//func (t *Tree) Ascend(iterator ItemIterator) {
// t.AscendGreaterOrEqual(Inf(-1), iterator)
//}
func (t *LLRB) AscendRange(greaterOrEqual, lessThan Item, iterator ItemIterator) {
t.ascendRange(t.root, greaterOrEqual, lessThan, iterator)
}
func (t *LLRB) ascendRange(h *Node, inf, sup Item, iterator ItemIterator) bool {
if h == nil {
return true
}
if !less(h.Item, sup) {
return t.ascendRange(h.Left, inf, sup, iterator)
}
if less(h.Item, inf) {
return t.ascendRange(h.Right, inf, sup, iterator)
}
if !t.ascendRange(h.Left, inf, sup, iterator) {
return false
}
if !iterator(h.Item) {
return false
}
return t.ascendRange(h.Right, inf, sup, iterator)
}
// AscendGreaterOrEqual will call iterator once for each element greater or equal to
// pivot in ascending order. It will stop whenever the iterator returns false.
func (t *LLRB) AscendGreaterOrEqual(pivot Item, iterator ItemIterator) {
t.ascendGreaterOrEqual(t.root, pivot, iterator)
}
func (t *LLRB) ascendGreaterOrEqual(h *Node, pivot Item, iterator ItemIterator) bool {
if h == nil {
return true
}
if !less(h.Item, pivot) {
if !t.ascendGreaterOrEqual(h.Left, pivot, iterator) {
return false
}
if !iterator(h.Item) {
return false
}
}
return t.ascendGreaterOrEqual(h.Right, pivot, iterator)
}
func (t *LLRB) AscendLessThan(pivot Item, iterator ItemIterator) {
t.ascendLessThan(t.root, pivot, iterator)
}
func (t *LLRB) ascendLessThan(h *Node, pivot Item, iterator ItemIterator) bool {
if h == nil {
return true
}
if !t.ascendLessThan(h.Left, pivot, iterator) {
return false
}
if !iterator(h.Item) {
return false
}
if less(h.Item, pivot) {
return t.ascendLessThan(h.Left, pivot, iterator)
}
return true
}
// DescendLessOrEqual will call iterator once for each element less than the
// pivot in descending order. It will stop whenever the iterator returns false.
func (t *LLRB) DescendLessOrEqual(pivot Item, iterator ItemIterator) {
t.descendLessOrEqual(t.root, pivot, iterator)
}
func (t *LLRB) descendLessOrEqual(h *Node, pivot Item, iterator ItemIterator) bool {
if h == nil {
return true
}
if less(h.Item, pivot) || !less(pivot, h.Item) {
if !t.descendLessOrEqual(h.Right, pivot, iterator) {
return false
}
if !iterator(h.Item) {
return false
}
}
return t.descendLessOrEqual(h.Left, pivot, iterator)
}

46
vendor/github.com/petar/GoLLRB/llrb/llrb-stats.go generated vendored Normal file
View file

@ -0,0 +1,46 @@
// Copyright 2010 Petar Maymounkov. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package llrb
// GetHeight() returns an item in the tree with key @key, and it's height in the tree
func (t *LLRB) GetHeight(key Item) (result Item, depth int) {
return t.getHeight(t.root, key)
}
func (t *LLRB) getHeight(h *Node, item Item) (Item, int) {
if h == nil {
return nil, 0
}
if less(item, h.Item) {
result, depth := t.getHeight(h.Left, item)
return result, depth + 1
}
if less(h.Item, item) {
result, depth := t.getHeight(h.Right, item)
return result, depth + 1
}
return h.Item, 0
}
// HeightStats() returns the average and standard deviation of the height
// of elements in the tree
func (t *LLRB) HeightStats() (avg, stddev float64) {
av := &avgVar{}
heightStats(t.root, 0, av)
return av.GetAvg(), av.GetStdDev()
}
func heightStats(h *Node, d int, av *avgVar) {
if h == nil {
return
}
av.Add(float64(d))
if h.Left != nil {
heightStats(h.Left, d+1, av)
}
if h.Right != nil {
heightStats(h.Right, d+1, av)
}
}

456
vendor/github.com/petar/GoLLRB/llrb/llrb.go generated vendored Normal file
View file

@ -0,0 +1,456 @@
// Copyright 2010 Petar Maymounkov. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// A Left-Leaning Red-Black (LLRB) implementation of 2-3 balanced binary search trees,
// based on the following work:
//
// http://www.cs.princeton.edu/~rs/talks/LLRB/08Penn.pdf
// http://www.cs.princeton.edu/~rs/talks/LLRB/LLRB.pdf
// http://www.cs.princeton.edu/~rs/talks/LLRB/Java/RedBlackBST.java
//
// 2-3 trees (and the run-time equivalent 2-3-4 trees) are the de facto standard BST
// algoritms found in implementations of Python, Java, and other libraries. The LLRB
// implementation of 2-3 trees is a recent improvement on the traditional implementation,
// observed and documented by Robert Sedgewick.
//
package llrb
// Tree is a Left-Leaning Red-Black (LLRB) implementation of 2-3 trees
type LLRB struct {
count int
root *Node
}
type Node struct {
Item
Left, Right *Node // Pointers to left and right child nodes
Black bool // If set, the color of the link (incoming from the parent) is black
// In the LLRB, new nodes are always red, hence the zero-value for node
}
type Item interface {
Less(than Item) bool
}
//
func less(x, y Item) bool {
if x == pinf {
return false
}
if x == ninf {
return true
}
return x.Less(y)
}
// Inf returns an Item that is "bigger than" any other item, if sign is positive.
// Otherwise it returns an Item that is "smaller than" any other item.
func Inf(sign int) Item {
if sign == 0 {
panic("sign")
}
if sign > 0 {
return pinf
}
return ninf
}
var (
ninf = nInf{}
pinf = pInf{}
)
type nInf struct{}
func (nInf) Less(Item) bool {
return true
}
type pInf struct{}
func (pInf) Less(Item) bool {
return false
}
// New() allocates a new tree
func New() *LLRB {
return &LLRB{}
}
// SetRoot sets the root node of the tree.
// It is intended to be used by functions that deserialize the tree.
func (t *LLRB) SetRoot(r *Node) {
t.root = r
}
// Root returns the root node of the tree.
// It is intended to be used by functions that serialize the tree.
func (t *LLRB) Root() *Node {
return t.root
}
// Len returns the number of nodes in the tree.
func (t *LLRB) Len() int { return t.count }
// Has returns true if the tree contains an element whose order is the same as that of key.
func (t *LLRB) Has(key Item) bool {
return t.Get(key) != nil
}
// Get retrieves an element from the tree whose order is the same as that of key.
func (t *LLRB) Get(key Item) Item {
h := t.root
for h != nil {
switch {
case less(key, h.Item):
h = h.Left
case less(h.Item, key):
h = h.Right
default:
return h.Item
}
}
return nil
}
// Min returns the minimum element in the tree.
func (t *LLRB) Min() Item {
h := t.root
if h == nil {
return nil
}
for h.Left != nil {
h = h.Left
}
return h.Item
}
// Max returns the maximum element in the tree.
func (t *LLRB) Max() Item {
h := t.root
if h == nil {
return nil
}
for h.Right != nil {
h = h.Right
}
return h.Item
}
func (t *LLRB) ReplaceOrInsertBulk(items ...Item) {
for _, i := range items {
t.ReplaceOrInsert(i)
}
}
func (t *LLRB) InsertNoReplaceBulk(items ...Item) {
for _, i := range items {
t.InsertNoReplace(i)
}
}
// ReplaceOrInsert inserts item into the tree. If an existing
// element has the same order, it is removed from the tree and returned.
func (t *LLRB) ReplaceOrInsert(item Item) Item {
if item == nil {
panic("inserting nil item")
}
var replaced Item
t.root, replaced = t.replaceOrInsert(t.root, item)
t.root.Black = true
if replaced == nil {
t.count++
}
return replaced
}
func (t *LLRB) replaceOrInsert(h *Node, item Item) (*Node, Item) {
if h == nil {
return newNode(item), nil
}
h = walkDownRot23(h)
var replaced Item
if less(item, h.Item) { // BUG
h.Left, replaced = t.replaceOrInsert(h.Left, item)
} else if less(h.Item, item) {
h.Right, replaced = t.replaceOrInsert(h.Right, item)
} else {
replaced, h.Item = h.Item, item
}
h = walkUpRot23(h)
return h, replaced
}
// InsertNoReplace inserts item into the tree. If an existing
// element has the same order, both elements remain in the tree.
func (t *LLRB) InsertNoReplace(item Item) {
if item == nil {
panic("inserting nil item")
}
t.root = t.insertNoReplace(t.root, item)
t.root.Black = true
t.count++
}
func (t *LLRB) insertNoReplace(h *Node, item Item) *Node {
if h == nil {
return newNode(item)
}
h = walkDownRot23(h)
if less(item, h.Item) {
h.Left = t.insertNoReplace(h.Left, item)
} else {
h.Right = t.insertNoReplace(h.Right, item)
}
return walkUpRot23(h)
}
// Rotation driver routines for 2-3 algorithm
func walkDownRot23(h *Node) *Node { return h }
func walkUpRot23(h *Node) *Node {
if isRed(h.Right) && !isRed(h.Left) {
h = rotateLeft(h)
}
if isRed(h.Left) && isRed(h.Left.Left) {
h = rotateRight(h)
}
if isRed(h.Left) && isRed(h.Right) {
flip(h)
}
return h
}
// Rotation driver routines for 2-3-4 algorithm
func walkDownRot234(h *Node) *Node {
if isRed(h.Left) && isRed(h.Right) {
flip(h)
}
return h
}
func walkUpRot234(h *Node) *Node {
if isRed(h.Right) && !isRed(h.Left) {
h = rotateLeft(h)
}
if isRed(h.Left) && isRed(h.Left.Left) {
h = rotateRight(h)
}
return h
}
// DeleteMin deletes the minimum element in the tree and returns the
// deleted item or nil otherwise.
func (t *LLRB) DeleteMin() Item {
var deleted Item
t.root, deleted = deleteMin(t.root)
if t.root != nil {
t.root.Black = true
}
if deleted != nil {
t.count--
}
return deleted
}
// deleteMin code for LLRB 2-3 trees
func deleteMin(h *Node) (*Node, Item) {
if h == nil {
return nil, nil
}
if h.Left == nil {
return nil, h.Item
}
if !isRed(h.Left) && !isRed(h.Left.Left) {
h = moveRedLeft(h)
}
var deleted Item
h.Left, deleted = deleteMin(h.Left)
return fixUp(h), deleted
}
// DeleteMax deletes the maximum element in the tree and returns
// the deleted item or nil otherwise
func (t *LLRB) DeleteMax() Item {
var deleted Item
t.root, deleted = deleteMax(t.root)
if t.root != nil {
t.root.Black = true
}
if deleted != nil {
t.count--
}
return deleted
}
func deleteMax(h *Node) (*Node, Item) {
if h == nil {
return nil, nil
}
if isRed(h.Left) {
h = rotateRight(h)
}
if h.Right == nil {
return nil, h.Item
}
if !isRed(h.Right) && !isRed(h.Right.Left) {
h = moveRedRight(h)
}
var deleted Item
h.Right, deleted = deleteMax(h.Right)
return fixUp(h), deleted
}
// Delete deletes an item from the tree whose key equals key.
// The deleted item is return, otherwise nil is returned.
func (t *LLRB) Delete(key Item) Item {
var deleted Item
t.root, deleted = t.delete(t.root, key)
if t.root != nil {
t.root.Black = true
}
if deleted != nil {
t.count--
}
return deleted
}
func (t *LLRB) delete(h *Node, item Item) (*Node, Item) {
var deleted Item
if h == nil {
return nil, nil
}
if less(item, h.Item) {
if h.Left == nil { // item not present. Nothing to delete
return h, nil
}
if !isRed(h.Left) && !isRed(h.Left.Left) {
h = moveRedLeft(h)
}
h.Left, deleted = t.delete(h.Left, item)
} else {
if isRed(h.Left) {
h = rotateRight(h)
}
// If @item equals @h.Item and no right children at @h
if !less(h.Item, item) && h.Right == nil {
return nil, h.Item
}
// PETAR: Added 'h.Right != nil' below
if h.Right != nil && !isRed(h.Right) && !isRed(h.Right.Left) {
h = moveRedRight(h)
}
// If @item equals @h.Item, and (from above) 'h.Right != nil'
if !less(h.Item, item) {
var subDeleted Item
h.Right, subDeleted = deleteMin(h.Right)
if subDeleted == nil {
panic("logic")
}
deleted, h.Item = h.Item, subDeleted
} else { // Else, @item is bigger than @h.Item
h.Right, deleted = t.delete(h.Right, item)
}
}
return fixUp(h), deleted
}
// Internal node manipulation routines
func newNode(item Item) *Node { return &Node{Item: item} }
func isRed(h *Node) bool {
if h == nil {
return false
}
return !h.Black
}
func rotateLeft(h *Node) *Node {
x := h.Right
if x.Black {
panic("rotating a black link")
}
h.Right = x.Left
x.Left = h
x.Black = h.Black
h.Black = false
return x
}
func rotateRight(h *Node) *Node {
x := h.Left
if x.Black {
panic("rotating a black link")
}
h.Left = x.Right
x.Right = h
x.Black = h.Black
h.Black = false
return x
}
// REQUIRE: Left and Right children must be present
func flip(h *Node) {
h.Black = !h.Black
h.Left.Black = !h.Left.Black
h.Right.Black = !h.Right.Black
}
// REQUIRE: Left and Right children must be present
func moveRedLeft(h *Node) *Node {
flip(h)
if isRed(h.Right.Left) {
h.Right = rotateRight(h.Right)
h = rotateLeft(h)
flip(h)
}
return h
}
// REQUIRE: Left and Right children must be present
func moveRedRight(h *Node) *Node {
flip(h)
if isRed(h.Left.Left) {
h = rotateRight(h)
flip(h)
}
return h
}
func fixUp(h *Node) *Node {
if isRed(h.Right) {
h = rotateLeft(h)
}
if isRed(h.Left) && isRed(h.Left.Left) {
h = rotateRight(h)
}
if isRed(h.Left) && isRed(h.Right) {
flip(h)
}
return h
}

17
vendor/github.com/petar/GoLLRB/llrb/util.go generated vendored Normal file
View file

@ -0,0 +1,17 @@
// Copyright 2010 Petar Maymounkov. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package llrb
type Int int
func (x Int) Less(than Item) bool {
return x < than.(Int)
}
type String string
func (x String) Less(than Item) bool {
return x < than.(String)
}

19
vendor/github.com/peterbourgon/diskv/LICENSE generated vendored Normal file
View file

@ -0,0 +1,19 @@
Copyright (c) 2011-2012 Peter Bourgon
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.

141
vendor/github.com/peterbourgon/diskv/README.md generated vendored Normal file
View file

@ -0,0 +1,141 @@
# What is diskv?
Diskv (disk-vee) is a simple, persistent key-value store written in the Go
language. It starts with an incredibly simple API for storing arbitrary data on
a filesystem by key, and builds several layers of performance-enhancing
abstraction on top. The end result is a conceptually simple, but highly
performant, disk-backed storage system.
[![Build Status][1]][2]
[1]: https://drone.io/github.com/peterbourgon/diskv/status.png
[2]: https://drone.io/github.com/peterbourgon/diskv/latest
# Installing
Install [Go 1][3], either [from source][4] or [with a prepackaged binary][5].
Then,
```bash
$ go get github.com/peterbourgon/diskv
```
[3]: http://golang.org
[4]: http://golang.org/doc/install/source
[5]: http://golang.org/doc/install
# Usage
```go
package main
import (
"fmt"
"github.com/peterbourgon/diskv"
)
func main() {
// Simplest transform function: put all the data files into the base dir.
flatTransform := func(s string) []string { return []string{} }
// Initialize a new diskv store, rooted at "my-data-dir", with a 1MB cache.
d := diskv.New(diskv.Options{
BasePath: "my-data-dir",
Transform: flatTransform,
CacheSizeMax: 1024 * 1024,
})
// Write three bytes to the key "alpha".
key := "alpha"
d.Write(key, []byte{'1', '2', '3'})
// Read the value back out of the store.
value, _ := d.Read(key)
fmt.Printf("%v\n", value)
// Erase the key+value from the store (and the disk).
d.Erase(key)
}
```
More complex examples can be found in the "examples" subdirectory.
# Theory
## Basic idea
At its core, diskv is a map of a key (`string`) to arbitrary data (`[]byte`).
The data is written to a single file on disk, with the same name as the key.
The key determines where that file will be stored, via a user-provided
`TransformFunc`, which takes a key and returns a slice (`[]string`)
corresponding to a path list where the key file will be stored. The simplest
TransformFunc,
```go
func SimpleTransform (key string) []string {
return []string{}
}
```
will place all keys in the same, base directory. The design is inspired by
[Redis diskstore][6]; a TransformFunc which emulates the default diskstore
behavior is available in the content-addressable-storage example.
[6]: http://groups.google.com/group/redis-db/browse_thread/thread/d444bc786689bde9?pli=1
**Note** that your TransformFunc should ensure that one valid key doesn't
transform to a subset of another valid key. That is, it shouldn't be possible
to construct valid keys that resolve to directory names. As a concrete example,
if your TransformFunc splits on every 3 characters, then
```go
d.Write("abcabc", val) // OK: written to <base>/abc/abc/abcabc
d.Write("abc", val) // Error: attempted write to <base>/abc/abc, but it's a directory
```
This will be addressed in an upcoming version of diskv.
Probably the most important design principle behind diskv is that your data is
always flatly available on the disk. diskv will never do anything that would
prevent you from accessing, copying, backing up, or otherwise interacting with
your data via common UNIX commandline tools.
## Adding a cache
An in-memory caching layer is provided by combining the BasicStore
functionality with a simple map structure, and keeping it up-to-date as
appropriate. Since the map structure in Go is not threadsafe, it's combined
with a RWMutex to provide safe concurrent access.
## Adding order
diskv is a key-value store and therefore inherently unordered. An ordering
system can be injected into the store by passing something which satisfies the
diskv.Index interface. (A default implementation, using Petar Maymounkov's
[LLRB tree][7], is provided.) Basically, diskv keeps an ordered (by a
user-provided Less function) index of the keys, which can be queried.
[7]: https://github.com/petar/GoLLRB
## Adding compression
Something which implements the diskv.Compression interface may be passed
during store creation, so that all Writes and Reads are filtered through
a compression/decompression pipeline. Several default implementations,
using stdlib compression algorithms, are provided. Note that data is cached
compressed; the cost of decompression is borne with each Read.
## Streaming
diskv also now provides ReadStream and WriteStream methods, to allow very large
data to be handled efficiently.
# Future plans
* Needs plenty of robust testing: huge datasets, etc...
* More thorough benchmarking
* Your suggestions for use-cases I haven't thought of

64
vendor/github.com/peterbourgon/diskv/compression.go generated vendored Normal file
View file

@ -0,0 +1,64 @@
package diskv
import (
"compress/flate"
"compress/gzip"
"compress/zlib"
"io"
)
// Compression is an interface that Diskv uses to implement compression of
// data. Writer takes a destination io.Writer and returns a WriteCloser that
// compresses all data written through it. Reader takes a source io.Reader and
// returns a ReadCloser that decompresses all data read through it. You may
// define these methods on your own type, or use one of the NewCompression
// helpers.
type Compression interface {
Writer(dst io.Writer) (io.WriteCloser, error)
Reader(src io.Reader) (io.ReadCloser, error)
}
// NewGzipCompression returns a Gzip-based Compression.
func NewGzipCompression() Compression {
return NewGzipCompressionLevel(flate.DefaultCompression)
}
// NewGzipCompressionLevel returns a Gzip-based Compression with the given level.
func NewGzipCompressionLevel(level int) Compression {
return &genericCompression{
wf: func(w io.Writer) (io.WriteCloser, error) { return gzip.NewWriterLevel(w, level) },
rf: func(r io.Reader) (io.ReadCloser, error) { return gzip.NewReader(r) },
}
}
// NewZlibCompression returns a Zlib-based Compression.
func NewZlibCompression() Compression {
return NewZlibCompressionLevel(flate.DefaultCompression)
}
// NewZlibCompressionLevel returns a Zlib-based Compression with the given level.
func NewZlibCompressionLevel(level int) Compression {
return NewZlibCompressionLevelDict(level, nil)
}
// NewZlibCompressionLevelDict returns a Zlib-based Compression with the given
// level, based on the given dictionary.
func NewZlibCompressionLevelDict(level int, dict []byte) Compression {
return &genericCompression{
func(w io.Writer) (io.WriteCloser, error) { return zlib.NewWriterLevelDict(w, level, dict) },
func(r io.Reader) (io.ReadCloser, error) { return zlib.NewReaderDict(r, dict) },
}
}
type genericCompression struct {
wf func(w io.Writer) (io.WriteCloser, error)
rf func(r io.Reader) (io.ReadCloser, error)
}
func (g *genericCompression) Writer(dst io.Writer) (io.WriteCloser, error) {
return g.wf(dst)
}
func (g *genericCompression) Reader(src io.Reader) (io.ReadCloser, error) {
return g.rf(src)
}

577
vendor/github.com/peterbourgon/diskv/diskv.go generated vendored Normal file
View file

@ -0,0 +1,577 @@
// Diskv (disk-vee) is a simple, persistent, key-value store.
// It stores all data flatly on the filesystem.
package diskv
import (
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"sync"
"syscall"
)
const (
defaultBasePath = "diskv"
defaultFilePerm os.FileMode = 0666
defaultPathPerm os.FileMode = 0777
)
var (
defaultTransform = func(s string) []string { return []string{} }
errCanceled = errors.New("canceled")
errEmptyKey = errors.New("empty key")
errBadKey = errors.New("bad key")
errImportDirectory = errors.New("can't import a directory")
)
// TransformFunction transforms a key into a slice of strings, with each
// element in the slice representing a directory in the file path where the
// key's entry will eventually be stored.
//
// For example, if TransformFunc transforms "abcdef" to ["ab", "cde", "f"],
// the final location of the data file will be <basedir>/ab/cde/f/abcdef
type TransformFunction func(s string) []string
// Options define a set of properties that dictate Diskv behavior.
// All values are optional.
type Options struct {
BasePath string
Transform TransformFunction
CacheSizeMax uint64 // bytes
PathPerm os.FileMode
FilePerm os.FileMode
Index Index
IndexLess LessFunction
Compression Compression
}
// Diskv implements the Diskv interface. You shouldn't construct Diskv
// structures directly; instead, use the New constructor.
type Diskv struct {
sync.RWMutex
Options
cache map[string][]byte
cacheSize uint64
}
// New returns an initialized Diskv structure, ready to use.
// If the path identified by baseDir already contains data,
// it will be accessible, but not yet cached.
func New(o Options) *Diskv {
if o.BasePath == "" {
o.BasePath = defaultBasePath
}
if o.Transform == nil {
o.Transform = defaultTransform
}
if o.PathPerm == 0 {
o.PathPerm = defaultPathPerm
}
if o.FilePerm == 0 {
o.FilePerm = defaultFilePerm
}
d := &Diskv{
Options: o,
cache: map[string][]byte{},
cacheSize: 0,
}
if d.Index != nil && d.IndexLess != nil {
d.Index.Initialize(d.IndexLess, d.Keys(nil))
}
return d
}
// Write synchronously writes the key-value pair to disk, making it immediately
// available for reads. Write relies on the filesystem to perform an eventual
// sync to physical media. If you need stronger guarantees, see WriteStream.
func (d *Diskv) Write(key string, val []byte) error {
return d.WriteStream(key, bytes.NewBuffer(val), false)
}
// WriteStream writes the data represented by the io.Reader to the disk, under
// the provided key. If sync is true, WriteStream performs an explicit sync on
// the file as soon as it's written.
//
// bytes.Buffer provides io.Reader semantics for basic data types.
func (d *Diskv) WriteStream(key string, r io.Reader, sync bool) error {
if len(key) <= 0 {
return errEmptyKey
}
d.Lock()
defer d.Unlock()
return d.writeStreamWithLock(key, r, sync)
}
// writeStream does no input validation checking.
// TODO: use atomic FS ops.
func (d *Diskv) writeStreamWithLock(key string, r io.Reader, sync bool) error {
if err := d.ensurePathWithLock(key); err != nil {
return fmt.Errorf("ensure path: %s", err)
}
mode := os.O_WRONLY | os.O_CREATE | os.O_TRUNC // overwrite if exists
f, err := os.OpenFile(d.completeFilename(key), mode, d.FilePerm)
if err != nil {
return fmt.Errorf("open file: %s", err)
}
wc := io.WriteCloser(&nopWriteCloser{f})
if d.Compression != nil {
wc, err = d.Compression.Writer(f)
if err != nil {
f.Close() // error deliberately ignored
return fmt.Errorf("compression writer: %s", err)
}
}
if _, err := io.Copy(wc, r); err != nil {
f.Close() // error deliberately ignored
return fmt.Errorf("i/o copy: %s", err)
}
if err := wc.Close(); err != nil {
f.Close() // error deliberately ignored
return fmt.Errorf("compression close: %s", err)
}
if sync {
if err := f.Sync(); err != nil {
f.Close() // error deliberately ignored
return fmt.Errorf("file sync: %s", err)
}
}
if err := f.Close(); err != nil {
return fmt.Errorf("file close: %s", err)
}
if d.Index != nil {
d.Index.Insert(key)
}
d.bustCacheWithLock(key) // cache only on read
return nil
}
// Import imports the source file into diskv under the destination key. If the
// destination key already exists, it's overwritten. If move is true, the
// source file is removed after a successful import.
func (d *Diskv) Import(srcFilename, dstKey string, move bool) (err error) {
if dstKey == "" {
return errEmptyKey
}
if fi, err := os.Stat(srcFilename); err != nil {
return err
} else if fi.IsDir() {
return errImportDirectory
}
d.Lock()
defer d.Unlock()
if err := d.ensurePathWithLock(dstKey); err != nil {
return fmt.Errorf("ensure path: %s", err)
}
if move {
if err := syscall.Rename(srcFilename, d.completeFilename(dstKey)); err == nil {
d.bustCacheWithLock(dstKey)
return nil
} else if err != syscall.EXDEV {
// If it failed due to being on a different device, fall back to copying
return err
}
}
f, err := os.Open(srcFilename)
if err != nil {
return err
}
defer f.Close()
err = d.writeStreamWithLock(dstKey, f, false)
if err == nil && move {
err = os.Remove(srcFilename)
}
return err
}
// Read reads the key and returns the value.
// If the key is available in the cache, Read won't touch the disk.
// If the key is not in the cache, Read will have the side-effect of
// lazily caching the value.
func (d *Diskv) Read(key string) ([]byte, error) {
rc, err := d.ReadStream(key, false)
if err != nil {
return []byte{}, err
}
defer rc.Close()
return ioutil.ReadAll(rc)
}
// ReadStream reads the key and returns the value (data) as an io.ReadCloser.
// If the value is cached from a previous read, and direct is false,
// ReadStream will use the cached value. Otherwise, it will return a handle to
// the file on disk, and cache the data on read.
//
// If direct is true, ReadStream will lazily delete any cached value for the
// key, and return a direct handle to the file on disk.
//
// If compression is enabled, ReadStream taps into the io.Reader stream prior
// to decompression, and caches the compressed data.
func (d *Diskv) ReadStream(key string, direct bool) (io.ReadCloser, error) {
d.RLock()
defer d.RUnlock()
if val, ok := d.cache[key]; ok {
if !direct {
buf := bytes.NewBuffer(val)
if d.Compression != nil {
return d.Compression.Reader(buf)
}
return ioutil.NopCloser(buf), nil
}
go func() {
d.Lock()
defer d.Unlock()
d.uncacheWithLock(key, uint64(len(val)))
}()
}
return d.readWithRLock(key)
}
// read ignores the cache, and returns an io.ReadCloser representing the
// decompressed data for the given key, streamed from the disk. Clients should
// acquire a read lock on the Diskv and check the cache themselves before
// calling read.
func (d *Diskv) readWithRLock(key string) (io.ReadCloser, error) {
filename := d.completeFilename(key)
fi, err := os.Stat(filename)
if err != nil {
return nil, err
}
if fi.IsDir() {
return nil, os.ErrNotExist
}
f, err := os.Open(filename)
if err != nil {
return nil, err
}
var r io.Reader
if d.CacheSizeMax > 0 {
r = newSiphon(f, d, key)
} else {
r = &closingReader{f}
}
var rc = io.ReadCloser(ioutil.NopCloser(r))
if d.Compression != nil {
rc, err = d.Compression.Reader(r)
if err != nil {
return nil, err
}
}
return rc, nil
}
// closingReader provides a Reader that automatically closes the
// embedded ReadCloser when it reaches EOF
type closingReader struct {
rc io.ReadCloser
}
func (cr closingReader) Read(p []byte) (int, error) {
n, err := cr.rc.Read(p)
if err == io.EOF {
if closeErr := cr.rc.Close(); closeErr != nil {
return n, closeErr // close must succeed for Read to succeed
}
}
return n, err
}
// siphon is like a TeeReader: it copies all data read through it to an
// internal buffer, and moves that buffer to the cache at EOF.
type siphon struct {
f *os.File
d *Diskv
key string
buf *bytes.Buffer
}
// newSiphon constructs a siphoning reader that represents the passed file.
// When a successful series of reads ends in an EOF, the siphon will write
// the buffered data to Diskv's cache under the given key.
func newSiphon(f *os.File, d *Diskv, key string) io.Reader {
return &siphon{
f: f,
d: d,
key: key,
buf: &bytes.Buffer{},
}
}
// Read implements the io.Reader interface for siphon.
func (s *siphon) Read(p []byte) (int, error) {
n, err := s.f.Read(p)
if err == nil {
return s.buf.Write(p[0:n]) // Write must succeed for Read to succeed
}
if err == io.EOF {
s.d.cacheWithoutLock(s.key, s.buf.Bytes()) // cache may fail
if closeErr := s.f.Close(); closeErr != nil {
return n, closeErr // close must succeed for Read to succeed
}
return n, err
}
return n, err
}
// Erase synchronously erases the given key from the disk and the cache.
func (d *Diskv) Erase(key string) error {
d.Lock()
defer d.Unlock()
d.bustCacheWithLock(key)
// erase from index
if d.Index != nil {
d.Index.Delete(key)
}
// erase from disk
filename := d.completeFilename(key)
if s, err := os.Stat(filename); err == nil {
if !!s.IsDir() {
return errBadKey
}
if err = os.Remove(filename); err != nil {
return fmt.Errorf("remove: %s", err)
}
} else {
return fmt.Errorf("stat: %s", err)
}
// clean up and return
d.pruneDirsWithLock(key)
return nil
}
// EraseAll will delete all of the data from the store, both in the cache and on
// the disk. Note that EraseAll doesn't distinguish diskv-related data from non-
// diskv-related data. Care should be taken to always specify a diskv base
// directory that is exclusively for diskv data.
func (d *Diskv) EraseAll() error {
d.Lock()
defer d.Unlock()
d.cache = make(map[string][]byte)
d.cacheSize = 0
return os.RemoveAll(d.BasePath)
}
// Has returns true if the given key exists.
func (d *Diskv) Has(key string) bool {
d.Lock()
defer d.Unlock()
if _, ok := d.cache[key]; ok {
return true
}
filename := d.completeFilename(key)
s, err := os.Stat(filename)
if err != nil {
return false
}
if s.IsDir() {
return false
}
return true
}
// Keys returns a channel that will yield every key accessible by the store,
// in undefined order. If a cancel channel is provided, closing it will
// terminate and close the keys channel.
func (d *Diskv) Keys(cancel <-chan struct{}) <-chan string {
return d.KeysPrefix("", cancel)
}
// KeysPrefix returns a channel that will yield every key accessible by the
// store with the given prefix, in undefined order. If a cancel channel is
// provided, closing it will terminate and close the keys channel. If the
// provided prefix is the empty string, all keys will be yielded.
func (d *Diskv) KeysPrefix(prefix string, cancel <-chan struct{}) <-chan string {
var prepath string
if prefix == "" {
prepath = d.BasePath
} else {
prepath = d.pathFor(prefix)
}
c := make(chan string)
go func() {
filepath.Walk(prepath, walker(c, prefix, cancel))
close(c)
}()
return c
}
// walker returns a function which satisfies the filepath.WalkFunc interface.
// It sends every non-directory file entry down the channel c.
func walker(c chan<- string, prefix string, cancel <-chan struct{}) filepath.WalkFunc {
return func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() || !strings.HasPrefix(info.Name(), prefix) {
return nil // "pass"
}
select {
case c <- info.Name():
case <-cancel:
return errCanceled
}
return nil
}
}
// pathFor returns the absolute path for location on the filesystem where the
// data for the given key will be stored.
func (d *Diskv) pathFor(key string) string {
return filepath.Join(d.BasePath, filepath.Join(d.Transform(key)...))
}
// ensurePathWithLock is a helper function that generates all necessary
// directories on the filesystem for the given key.
func (d *Diskv) ensurePathWithLock(key string) error {
return os.MkdirAll(d.pathFor(key), d.PathPerm)
}
// completeFilename returns the absolute path to the file for the given key.
func (d *Diskv) completeFilename(key string) string {
return filepath.Join(d.pathFor(key), key)
}
// cacheWithLock attempts to cache the given key-value pair in the store's
// cache. It can fail if the value is larger than the cache's maximum size.
func (d *Diskv) cacheWithLock(key string, val []byte) error {
valueSize := uint64(len(val))
if err := d.ensureCacheSpaceWithLock(valueSize); err != nil {
return fmt.Errorf("%s; not caching", err)
}
// be very strict about memory guarantees
if (d.cacheSize + valueSize) > d.CacheSizeMax {
panic(fmt.Sprintf("failed to make room for value (%d/%d)", valueSize, d.CacheSizeMax))
}
d.cache[key] = val
d.cacheSize += valueSize
return nil
}
// cacheWithoutLock acquires the store's (write) mutex and calls cacheWithLock.
func (d *Diskv) cacheWithoutLock(key string, val []byte) error {
d.Lock()
defer d.Unlock()
return d.cacheWithLock(key, val)
}
func (d *Diskv) bustCacheWithLock(key string) {
if val, ok := d.cache[key]; ok {
d.uncacheWithLock(key, uint64(len(val)))
}
}
func (d *Diskv) uncacheWithLock(key string, sz uint64) {
d.cacheSize -= sz
delete(d.cache, key)
}
// pruneDirsWithLock deletes empty directories in the path walk leading to the
// key k. Typically this function is called after an Erase is made.
func (d *Diskv) pruneDirsWithLock(key string) error {
pathlist := d.Transform(key)
for i := range pathlist {
dir := filepath.Join(d.BasePath, filepath.Join(pathlist[:len(pathlist)-i]...))
// thanks to Steven Blenkinsop for this snippet
switch fi, err := os.Stat(dir); true {
case err != nil:
return err
case !fi.IsDir():
panic(fmt.Sprintf("corrupt dirstate at %s", dir))
}
nlinks, err := filepath.Glob(filepath.Join(dir, "*"))
if err != nil {
return err
} else if len(nlinks) > 0 {
return nil // has subdirs -- do not prune
}
if err = os.Remove(dir); err != nil {
return err
}
}
return nil
}
// ensureCacheSpaceWithLock deletes entries from the cache in arbitrary order
// until the cache has at least valueSize bytes available.
func (d *Diskv) ensureCacheSpaceWithLock(valueSize uint64) error {
if valueSize > d.CacheSizeMax {
return fmt.Errorf("value size (%d bytes) too large for cache (%d bytes)", valueSize, d.CacheSizeMax)
}
safe := func() bool { return (d.cacheSize + valueSize) <= d.CacheSizeMax }
for key, val := range d.cache {
if safe() {
break
}
d.uncacheWithLock(key, uint64(len(val)))
}
if !safe() {
panic(fmt.Sprintf("%d bytes still won't fit in the cache! (max %d bytes)", valueSize, d.CacheSizeMax))
}
return nil
}
// nopWriteCloser wraps an io.Writer and provides a no-op Close method to
// satisfy the io.WriteCloser interface.
type nopWriteCloser struct {
io.Writer
}
func (wc *nopWriteCloser) Write(p []byte) (int, error) { return wc.Writer.Write(p) }
func (wc *nopWriteCloser) Close() error { return nil }

116
vendor/github.com/peterbourgon/diskv/index.go generated vendored Normal file
View file

@ -0,0 +1,116 @@
package diskv
import (
"sync"
"github.com/petar/GoLLRB/llrb"
)
// Index is a generic interface for things that can
// provide an ordered list of keys.
type Index interface {
Initialize(less LessFunction, keys <-chan string)
Insert(key string)
Delete(key string)
Keys(from string, n int) []string
}
// LessFunction is used to initialize an Index of keys in a specific order.
type LessFunction func(string, string) bool
// llrbString is a custom data type that satisfies the LLRB Less interface,
// making the strings it wraps sortable by the LLRB package.
type llrbString struct {
s string
l LessFunction
}
// Less satisfies the llrb.Less interface using the llrbString's LessFunction.
func (s llrbString) Less(i llrb.Item) bool {
return s.l(s.s, i.(llrbString).s)
}
// LLRBIndex is an implementation of the Index interface
// using Petar Maymounkov's LLRB tree.
type LLRBIndex struct {
sync.RWMutex
LessFunction
*llrb.LLRB
}
// Initialize populates the LLRB tree with data from the keys channel,
// according to the passed less function. It's destructive to the LLRBIndex.
func (i *LLRBIndex) Initialize(less LessFunction, keys <-chan string) {
i.Lock()
defer i.Unlock()
i.LessFunction = less
i.LLRB = rebuild(less, keys)
}
// Insert inserts the given key (only) into the LLRB tree.
func (i *LLRBIndex) Insert(key string) {
i.Lock()
defer i.Unlock()
if i.LLRB == nil || i.LessFunction == nil {
panic("uninitialized index")
}
i.LLRB.ReplaceOrInsert(llrbString{s: key, l: i.LessFunction})
}
// Delete removes the given key (only) from the LLRB tree.
func (i *LLRBIndex) Delete(key string) {
i.Lock()
defer i.Unlock()
if i.LLRB == nil || i.LessFunction == nil {
panic("uninitialized index")
}
i.LLRB.Delete(llrbString{s: key, l: i.LessFunction})
}
// Keys yields a maximum of n keys in order. If the passed 'from' key is empty,
// Keys will return the first n keys. If the passed 'from' key is non-empty, the
// first key in the returned slice will be the key that immediately follows the
// passed key, in key order.
func (i *LLRBIndex) Keys(from string, n int) []string {
i.RLock()
defer i.RUnlock()
if i.LLRB == nil || i.LessFunction == nil {
panic("uninitialized index")
}
if i.LLRB.Len() <= 0 {
return []string{}
}
llrbFrom := llrbString{s: from, l: i.LessFunction}
skipFirst := true
if len(from) <= 0 || !i.LLRB.Has(llrbFrom) {
// no such key, so start at the top
llrbFrom = i.LLRB.Min().(llrbString)
skipFirst = false
}
keys := []string{}
iterator := func(i llrb.Item) bool {
keys = append(keys, i.(llrbString).s)
return len(keys) < n
}
i.LLRB.AscendGreaterOrEqual(llrbFrom, iterator)
if skipFirst && len(keys) > 0 {
keys = keys[1:]
}
return keys
}
// rebuildIndex does the work of regenerating the index
// with the given keys.
func rebuild(less LessFunction, keys <-chan string) *llrb.LLRB {
tree := llrb.New()
for key := range keys {
tree.ReplaceOrInsert(llrbString{s: key, l: less})
}
return tree
}