diff options
106 files changed, 4248 insertions, 5780 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ee47ec35..64cc57ab8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Mattermost Changelog -## UNDER DEVELOPMENT - Release v1.0.0 +## UNDER DEVELOPMENT - Release v1.0.0-RC1 The "UNDER DEVELOPMENT" section of the Mattermost changelog appears in the product's `master` branch to note key changes committed to master and are on their way to the next stable release. When a stable release is pushed the "UNDER DEVELOPMENT" heading is removed from the final changelog of the release. @@ -9,7 +9,10 @@ The "UNDER DEVELOPMENT" section of the Mattermost changelog appears in the produ ### Release Highlights -- [See Product Roadmap for anticipated features](https://mattermost.atlassian.net/issues/?filter=10002) +- System Console - UI for configuring deployments, managing teams, resetting user passwords and other admin features +- Markdown support in messages, comments and channel descriptions - Including font formatting, emoticons, headings and tables +- Preset themes and detailed theme color options, plus ability to import themes from Slack +- Numerous performance improvements and optimizations ### New Features @@ -17,12 +20,17 @@ Messaging, Comments and Notifications - Support for emoji codes rendering to image files - Full markdown support in messages, comments, and channel description + +Files and Images + - Added ability to play video and audio files System Console - UI to change config.json settings - Ability to view log files from console +- Ability to reset user passwords +- Ability for IT admin to manage members across multiple teams from single interface User Interface @@ -65,6 +73,7 @@ UI Many thanks to our external contributors. In no particular order: +- [jdeng](https://github.com/jdeng) - [Trozz](https://github.com/Trozz) - [LAndres](https://github.com/LAndreas) - [JessBot](https://github.com/JessBot) diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 9bd02d455..7c80558c4 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -13,11 +13,6 @@ "Rev": "69e2a90ed92d03812364aeb947b7068dc42e561e" }, { - "ImportPath": "code.google.com/p/graphics-go/graphics", - "Comment": "null-25", - "Rev": "f843bfcd8ac420072c7f6804599995b0a229070b" - }, - { "ImportPath": "code.google.com/p/log4go", "Comment": "go.weekly.2012-02-22-1", "Rev": "c3294304d93f48a37d3bed1d382882a9c2989f99" @@ -32,6 +27,10 @@ "Rev": "5280e250f2795914acbeb2bf3b55dd5a2d1fba52" }, { + "ImportPath": "github.com/disintegration/imaging", + "Rev": "493653de80c32beeae336f3a3a3a125e7603459b" + }, + { "ImportPath": "github.com/garyburd/redigo/internal", "Rev": "a47585eaae68b1d14b02940d2af1b9194f3caa9c" }, @@ -89,10 +88,6 @@ "Rev": "a163d6a569f1cd264d2f8b2bf3c5d04ace5995eb" }, { - "ImportPath": "github.com/nfnt/resize", - "Rev": "dc93e1b98c579d90ee2fa15c1fd6dac34f6e7899" - }, - { "ImportPath": "github.com/rwcarlsen/goexif/exif", "Rev": "709fab3d192d7c62f86043caff1e7e3fb0f42bd8" }, @@ -137,6 +132,10 @@ "Rev": "baddd3465a05d84a6d8d3507547a91cb188c81ea" }, { + "ImportPath": "golang.org/x/image/tiff", + "Rev": "baddd3465a05d84a6d8d3507547a91cb188c81ea" + }, + { "ImportPath": "gopkg.in/fsnotify.v1", "Comment": "v1.2.0", "Rev": "96c060f6a6b7e0d6f75fddd10efeaca3e5d1bcb0" diff --git a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/Makefile b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/Makefile deleted file mode 100644 index 28a06f0e8..000000000 --- a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/Makefile +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright 2011 The Graphics-Go Authors. All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -include $(GOROOT)/src/Make.inc - -TARG=code.google.com/p/graphics-go/graphics -GOFILES=\ - affine.go\ - blur.go\ - rotate.go\ - scale.go\ - thumbnail.go\ - -include $(GOROOT)/src/Make.pkg diff --git a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/affine.go b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/affine.go deleted file mode 100644 index 0ac2ec9da..000000000 --- a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/affine.go +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright 2011 The Graphics-Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package graphics - -import ( - "code.google.com/p/graphics-go/graphics/interp" - "errors" - "image" - "image/draw" - "math" -) - -// I is the identity Affine transform matrix. -var I = Affine{ - 1, 0, 0, - 0, 1, 0, - 0, 0, 1, -} - -// Affine is a 3x3 2D affine transform matrix. -// M(i,j) is Affine[i*3+j]. -type Affine [9]float64 - -// Mul returns the multiplication of two affine transform matrices. -func (a Affine) Mul(b Affine) Affine { - return Affine{ - a[0]*b[0] + a[1]*b[3] + a[2]*b[6], - a[0]*b[1] + a[1]*b[4] + a[2]*b[7], - a[0]*b[2] + a[1]*b[5] + a[2]*b[8], - a[3]*b[0] + a[4]*b[3] + a[5]*b[6], - a[3]*b[1] + a[4]*b[4] + a[5]*b[7], - a[3]*b[2] + a[4]*b[5] + a[5]*b[8], - a[6]*b[0] + a[7]*b[3] + a[8]*b[6], - a[6]*b[1] + a[7]*b[4] + a[8]*b[7], - a[6]*b[2] + a[7]*b[5] + a[8]*b[8], - } -} - -func (a Affine) transformRGBA(dst *image.RGBA, src *image.RGBA, i interp.RGBA) error { - srcb := src.Bounds() - b := dst.Bounds() - for y := b.Min.Y; y < b.Max.Y; y++ { - for x := b.Min.X; x < b.Max.X; x++ { - sx, sy := a.pt(x, y) - if inBounds(srcb, sx, sy) { - c := i.RGBA(src, sx, sy) - off := (y-dst.Rect.Min.Y)*dst.Stride + (x-dst.Rect.Min.X)*4 - dst.Pix[off+0] = c.R - dst.Pix[off+1] = c.G - dst.Pix[off+2] = c.B - dst.Pix[off+3] = c.A - } - } - } - return nil -} - -// Transform applies the affine transform to src and produces dst. -func (a Affine) Transform(dst draw.Image, src image.Image, i interp.Interp) error { - if dst == nil { - return errors.New("graphics: dst is nil") - } - if src == nil { - return errors.New("graphics: src is nil") - } - - // RGBA fast path. - dstRGBA, dstOk := dst.(*image.RGBA) - srcRGBA, srcOk := src.(*image.RGBA) - interpRGBA, interpOk := i.(interp.RGBA) - if dstOk && srcOk && interpOk { - return a.transformRGBA(dstRGBA, srcRGBA, interpRGBA) - } - - srcb := src.Bounds() - b := dst.Bounds() - for y := b.Min.Y; y < b.Max.Y; y++ { - for x := b.Min.X; x < b.Max.X; x++ { - sx, sy := a.pt(x, y) - if inBounds(srcb, sx, sy) { - dst.Set(x, y, i.Interp(src, sx, sy)) - } - } - } - return nil -} - -func inBounds(b image.Rectangle, x, y float64) bool { - if x < float64(b.Min.X) || x >= float64(b.Max.X) { - return false - } - if y < float64(b.Min.Y) || y >= float64(b.Max.Y) { - return false - } - return true -} - -func (a Affine) pt(x0, y0 int) (x1, y1 float64) { - fx := float64(x0) + 0.5 - fy := float64(y0) + 0.5 - x1 = fx*a[0] + fy*a[1] + a[2] - y1 = fx*a[3] + fy*a[4] + a[5] - return x1, y1 -} - -// TransformCenter applies the affine transform to src and produces dst. -// Equivalent to -// a.CenterFit(dst, src).Transform(dst, src, i). -func (a Affine) TransformCenter(dst draw.Image, src image.Image, i interp.Interp) error { - if dst == nil { - return errors.New("graphics: dst is nil") - } - if src == nil { - return errors.New("graphics: src is nil") - } - - return a.CenterFit(dst.Bounds(), src.Bounds()).Transform(dst, src, i) -} - -// Scale produces a scaling transform of factors x and y. -func (a Affine) Scale(x, y float64) Affine { - return a.Mul(Affine{ - 1 / x, 0, 0, - 0, 1 / y, 0, - 0, 0, 1, - }) -} - -// Rotate produces a clockwise rotation transform of angle, in radians. -func (a Affine) Rotate(angle float64) Affine { - s, c := math.Sincos(angle) - return a.Mul(Affine{ - +c, +s, +0, - -s, +c, +0, - +0, +0, +1, - }) -} - -// Shear produces a shear transform by the slopes x and y. -func (a Affine) Shear(x, y float64) Affine { - d := 1 - x*y - return a.Mul(Affine{ - +1 / d, -x / d, 0, - -y / d, +1 / d, 0, - 0, 0, 1, - }) -} - -// Translate produces a translation transform with pixel distances x and y. -func (a Affine) Translate(x, y float64) Affine { - return a.Mul(Affine{ - 1, 0, -x, - 0, 1, -y, - 0, 0, +1, - }) -} - -// Center produces the affine transform, centered around the provided point. -func (a Affine) Center(x, y float64) Affine { - return I.Translate(-x, -y).Mul(a).Translate(x, y) -} - -// CenterFit produces the affine transform, centered around the rectangles. -// It is equivalent to -// I.Translate(-<center of src>).Mul(a).Translate(<center of dst>) -func (a Affine) CenterFit(dst, src image.Rectangle) Affine { - dx := float64(dst.Min.X) + float64(dst.Dx())/2 - dy := float64(dst.Min.Y) + float64(dst.Dy())/2 - sx := float64(src.Min.X) + float64(src.Dx())/2 - sy := float64(src.Min.Y) + float64(src.Dy())/2 - return I.Translate(-sx, -sy).Mul(a).Translate(dx, dy) -} diff --git a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/blur.go b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/blur.go deleted file mode 100644 index 9a54d5ad5..000000000 --- a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/blur.go +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2011 The Graphics-Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package graphics - -import ( - "code.google.com/p/graphics-go/graphics/convolve" - "errors" - "image" - "image/draw" - "math" -) - -// DefaultStdDev is the default blurring parameter. -var DefaultStdDev = 0.5 - -// BlurOptions are the blurring parameters. -// StdDev is the standard deviation of the normal, higher is blurrier. -// Size is the size of the kernel. If zero, it is set to Ceil(6 * StdDev). -type BlurOptions struct { - StdDev float64 - Size int -} - -// Blur produces a blurred version of the image, using a Gaussian blur. -func Blur(dst draw.Image, src image.Image, opt *BlurOptions) error { - if dst == nil { - return errors.New("graphics: dst is nil") - } - if src == nil { - return errors.New("graphics: src is nil") - } - - sd := DefaultStdDev - size := 0 - - if opt != nil { - sd = opt.StdDev - size = opt.Size - } - - if size < 1 { - size = int(math.Ceil(sd * 6)) - } - - kernel := make([]float64, 2*size+1) - for i := 0; i <= size; i++ { - x := float64(i) / sd - x = math.Pow(1/math.SqrtE, x*x) - kernel[size-i] = x - kernel[size+i] = x - } - - // Normalize the weights to sum to 1.0. - kSum := 0.0 - for _, k := range kernel { - kSum += k - } - for i, k := range kernel { - kernel[i] = k / kSum - } - - return convolve.Convolve(dst, src, &convolve.SeparableKernel{ - X: kernel, - Y: kernel, - }) -} diff --git a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/blur_test.go b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/blur_test.go deleted file mode 100644 index 1d84fa604..000000000 --- a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/blur_test.go +++ /dev/null @@ -1,207 +0,0 @@ -// Copyright 2011 The Graphics-Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package graphics - -import ( - "code.google.com/p/graphics-go/graphics/graphicstest" - "image" - "image/color" - "testing" - - _ "image/png" -) - -var blurOneColorTests = []transformOneColorTest{ - { - "1x1-blank", 1, 1, 1, 1, - &BlurOptions{0.83, 1}, - []uint8{0xff}, - []uint8{0xff}, - }, - { - "1x1-spreadblank", 1, 1, 1, 1, - &BlurOptions{0.83, 2}, - []uint8{0xff}, - []uint8{0xff}, - }, - { - "3x3-blank", 3, 3, 3, 3, - &BlurOptions{0.83, 2}, - []uint8{ - 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, - }, - []uint8{ - 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, - }, - }, - { - "3x3-dot", 3, 3, 3, 3, - &BlurOptions{0.34, 1}, - []uint8{ - 0x00, 0x00, 0x00, - 0x00, 0xff, 0x00, - 0x00, 0x00, 0x00, - }, - []uint8{ - 0x00, 0x03, 0x00, - 0x03, 0xf2, 0x03, - 0x00, 0x03, 0x00, - }, - }, - { - "5x5-dot", 5, 5, 5, 5, - &BlurOptions{0.34, 1}, - []uint8{ - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0xff, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - }, - []uint8{ - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x03, 0x00, 0x00, - 0x00, 0x03, 0xf2, 0x03, 0x00, - 0x00, 0x00, 0x03, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - }, - }, - { - "5x5-dot-spread", 5, 5, 5, 5, - &BlurOptions{0.85, 1}, - []uint8{ - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0xff, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - }, - []uint8{ - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x10, 0x20, 0x10, 0x00, - 0x00, 0x20, 0x40, 0x20, 0x00, - 0x00, 0x10, 0x20, 0x10, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - }, - }, - { - "4x4-box", 4, 4, 4, 4, - &BlurOptions{0.34, 1}, - []uint8{ - 0x00, 0x00, 0x00, 0x00, - 0x00, 0xff, 0xff, 0x00, - 0x00, 0xff, 0xff, 0x00, - 0x00, 0x00, 0x00, 0x00, - }, - []uint8{ - 0x00, 0x03, 0x03, 0x00, - 0x03, 0xf8, 0xf8, 0x03, - 0x03, 0xf8, 0xf8, 0x03, - 0x00, 0x03, 0x03, 0x00, - }, - }, - { - "5x5-twodots", 5, 5, 5, 5, - &BlurOptions{0.34, 1}, - []uint8{ - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x96, 0x00, 0x96, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - }, - []uint8{ - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x02, 0x00, 0x02, 0x00, - 0x02, 0x8e, 0x04, 0x8e, 0x02, - 0x00, 0x02, 0x00, 0x02, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - }, - }, -} - -func TestBlurOneColor(t *testing.T) { - for _, oc := range blurOneColorTests { - dst := oc.newDst() - src := oc.newSrc() - opt := oc.opt.(*BlurOptions) - if err := Blur(dst, src, opt); err != nil { - t.Fatal(err) - } - - if !checkTransformTest(t, &oc, dst) { - continue - } - } -} - -func TestBlurEmpty(t *testing.T) { - empty := image.NewRGBA(image.Rect(0, 0, 0, 0)) - if err := Blur(empty, empty, nil); err != nil { - t.Fatal(err) - } -} - -func TestBlurGopher(t *testing.T) { - src, err := graphicstest.LoadImage("../testdata/gopher.png") - if err != nil { - t.Fatal(err) - } - - dst := image.NewRGBA(src.Bounds()) - if err = Blur(dst, src, &BlurOptions{StdDev: 1.1}); err != nil { - t.Fatal(err) - } - - cmp, err := graphicstest.LoadImage("../testdata/gopher-blur.png") - if err != nil { - t.Fatal(err) - } - err = graphicstest.ImageWithinTolerance(dst, cmp, 0x101) - if err != nil { - t.Fatal(err) - } -} - -func benchBlur(b *testing.B, bounds image.Rectangle) { - b.StopTimer() - - // Construct a fuzzy image. - src := image.NewRGBA(bounds) - for y := bounds.Min.Y; y < bounds.Max.Y; y++ { - for x := bounds.Min.X; x < bounds.Max.X; x++ { - src.SetRGBA(x, y, color.RGBA{ - uint8(5 * x % 0x100), - uint8(7 * y % 0x100), - uint8((7*x + 5*y) % 0x100), - 0xff, - }) - } - } - dst := image.NewRGBA(bounds) - - b.StartTimer() - for i := 0; i < b.N; i++ { - Blur(dst, src, &BlurOptions{0.84, 3}) - } -} - -func BenchmarkBlur400x400x3(b *testing.B) { - benchBlur(b, image.Rect(0, 0, 400, 400)) -} - -// Exactly twice the pixel count of 400x400. -func BenchmarkBlur400x800x3(b *testing.B) { - benchBlur(b, image.Rect(0, 0, 400, 800)) -} - -// Exactly twice the pixel count of 400x800 -func BenchmarkBlur400x1600x3(b *testing.B) { - benchBlur(b, image.Rect(0, 0, 400, 1600)) -} diff --git a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/convolve/Makefile b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/convolve/Makefile deleted file mode 100644 index a5691fa30..000000000 --- a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/convolve/Makefile +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright 2011 The Graphics-Go Authors. All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -include $(GOROOT)/src/Make.inc - -TARG=code.google.com/p/graphics-go/graphics/convolve -GOFILES=\ - convolve.go\ - -include $(GOROOT)/src/Make.pkg diff --git a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/convolve/convolve.go b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/convolve/convolve.go deleted file mode 100644 index da69496d0..000000000 --- a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/convolve/convolve.go +++ /dev/null @@ -1,274 +0,0 @@ -// Copyright 2011 The Graphics-Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package convolve - -import ( - "errors" - "fmt" - "image" - "image/draw" - "math" -) - -// clamp clamps x to the range [x0, x1]. -func clamp(x, x0, x1 float64) float64 { - if x < x0 { - return x0 - } - if x > x1 { - return x1 - } - return x -} - -// Kernel is a square matrix that defines a convolution. -type Kernel interface { - // Weights returns the square matrix of weights in row major order. - Weights() []float64 -} - -// SeparableKernel is a linearly separable, square convolution kernel. -// X and Y are the per-axis weights. Each slice must be the same length, and -// have an odd length. The middle element of each slice is the weight for the -// central pixel. For example, the horizontal Sobel kernel is: -// sobelX := &SeparableKernel{ -// X: []float64{-1, 0, +1}, -// Y: []float64{1, 2, 1}, -// } -type SeparableKernel struct { - X, Y []float64 -} - -func (k *SeparableKernel) Weights() []float64 { - n := len(k.X) - w := make([]float64, n*n) - for y := 0; y < n; y++ { - for x := 0; x < n; x++ { - w[y*n+x] = k.X[x] * k.Y[y] - } - } - return w -} - -// fullKernel is a square convolution kernel. -type fullKernel []float64 - -func (k fullKernel) Weights() []float64 { return k } - -func kernelSize(w []float64) (size int, err error) { - size = int(math.Sqrt(float64(len(w)))) - if size*size != len(w) { - return 0, errors.New("graphics: kernel is not square") - } - if size%2 != 1 { - return 0, errors.New("graphics: kernel size is not odd") - } - return size, nil -} - -// NewKernel returns a square convolution kernel. -func NewKernel(w []float64) (Kernel, error) { - if _, err := kernelSize(w); err != nil { - return nil, err - } - return fullKernel(w), nil -} - -func convolveRGBASep(dst *image.RGBA, src image.Image, k *SeparableKernel) error { - if len(k.X) != len(k.Y) { - return fmt.Errorf("graphics: kernel not square (x %d, y %d)", len(k.X), len(k.Y)) - } - if len(k.X)%2 != 1 { - return fmt.Errorf("graphics: kernel length (%d) not odd", len(k.X)) - } - radius := (len(k.X) - 1) / 2 - - // buf holds the result of vertically blurring src. - bounds := dst.Bounds() - width, height := bounds.Dx(), bounds.Dy() - buf := make([]float64, width*height*4) - for y := bounds.Min.Y; y < bounds.Max.Y; y++ { - for x := bounds.Min.X; x < bounds.Max.X; x++ { - var r, g, b, a float64 - // k0 is the kernel weight for the center pixel. This may be greater - // than kernel[0], near the boundary of the source image, to avoid - // vignetting. - k0 := k.X[radius] - - // Add the pixels from above. - for i := 1; i <= radius; i++ { - f := k.Y[radius-i] - if y-i < bounds.Min.Y { - k0 += f - } else { - or, og, ob, oa := src.At(x, y-i).RGBA() - r += float64(or>>8) * f - g += float64(og>>8) * f - b += float64(ob>>8) * f - a += float64(oa>>8) * f - } - } - - // Add the pixels from below. - for i := 1; i <= radius; i++ { - f := k.Y[radius+i] - if y+i >= bounds.Max.Y { - k0 += f - } else { - or, og, ob, oa := src.At(x, y+i).RGBA() - r += float64(or>>8) * f - g += float64(og>>8) * f - b += float64(ob>>8) * f - a += float64(oa>>8) * f - } - } - - // Add the central pixel. - or, og, ob, oa := src.At(x, y).RGBA() - r += float64(or>>8) * k0 - g += float64(og>>8) * k0 - b += float64(ob>>8) * k0 - a += float64(oa>>8) * k0 - - // Write to buf. - o := (y-bounds.Min.Y)*width*4 + (x-bounds.Min.X)*4 - buf[o+0] = r - buf[o+1] = g - buf[o+2] = b - buf[o+3] = a - } - } - - // dst holds the result of horizontally blurring buf. - for y := 0; y < height; y++ { - for x := 0; x < width; x++ { - var r, g, b, a float64 - k0, off := k.X[radius], y*width*4+x*4 - - // Add the pixels from the left. - for i := 1; i <= radius; i++ { - f := k.X[radius-i] - if x-i < 0 { - k0 += f - } else { - o := off - i*4 - r += buf[o+0] * f - g += buf[o+1] * f - b += buf[o+2] * f - a += buf[o+3] * f - } - } - - // Add the pixels from the right. - for i := 1; i <= radius; i++ { - f := k.X[radius+i] - if x+i >= width { - k0 += f - } else { - o := off + i*4 - r += buf[o+0] * f - g += buf[o+1] * f - b += buf[o+2] * f - a += buf[o+3] * f - } - } - - // Add the central pixel. - r += buf[off+0] * k0 - g += buf[off+1] * k0 - b += buf[off+2] * k0 - a += buf[off+3] * k0 - - // Write to dst, clamping to the range [0, 255]. - dstOff := (y-dst.Rect.Min.Y)*dst.Stride + (x-dst.Rect.Min.X)*4 - dst.Pix[dstOff+0] = uint8(clamp(r+0.5, 0, 255)) - dst.Pix[dstOff+1] = uint8(clamp(g+0.5, 0, 255)) - dst.Pix[dstOff+2] = uint8(clamp(b+0.5, 0, 255)) - dst.Pix[dstOff+3] = uint8(clamp(a+0.5, 0, 255)) - } - } - - return nil -} - -func convolveRGBA(dst *image.RGBA, src image.Image, k Kernel) error { - b := dst.Bounds() - bs := src.Bounds() - w := k.Weights() - size, err := kernelSize(w) - if err != nil { - return err - } - radius := (size - 1) / 2 - - for y := b.Min.Y; y < b.Max.Y; y++ { - for x := b.Min.X; x < b.Max.X; x++ { - if !image.Pt(x, y).In(bs) { - continue - } - - var r, g, b, a, adj float64 - for cy := y - radius; cy <= y+radius; cy++ { - for cx := x - radius; cx <= x+radius; cx++ { - factor := w[(cy-y+radius)*size+cx-x+radius] - if !image.Pt(cx, cy).In(bs) { - adj += factor - } else { - sr, sg, sb, sa := src.At(cx, cy).RGBA() - r += float64(sr>>8) * factor - g += float64(sg>>8) * factor - b += float64(sb>>8) * factor - a += float64(sa>>8) * factor - } - } - } - - if adj != 0 { - sr, sg, sb, sa := src.At(x, y).RGBA() - r += float64(sr>>8) * adj - g += float64(sg>>8) * adj - b += float64(sb>>8) * adj - a += float64(sa>>8) * adj - } - - off := (y-dst.Rect.Min.Y)*dst.Stride + (x-dst.Rect.Min.X)*4 - dst.Pix[off+0] = uint8(clamp(r+0.5, 0, 0xff)) - dst.Pix[off+1] = uint8(clamp(g+0.5, 0, 0xff)) - dst.Pix[off+2] = uint8(clamp(b+0.5, 0, 0xff)) - dst.Pix[off+3] = uint8(clamp(a+0.5, 0, 0xff)) - } - } - - return nil -} - -// Convolve produces dst by applying the convolution kernel k to src. -func Convolve(dst draw.Image, src image.Image, k Kernel) (err error) { - if dst == nil || src == nil || k == nil { - return nil - } - - b := dst.Bounds() - dstRgba, ok := dst.(*image.RGBA) - if !ok { - dstRgba = image.NewRGBA(b) - } - - switch k := k.(type) { - case *SeparableKernel: - err = convolveRGBASep(dstRgba, src, k) - default: - err = convolveRGBA(dstRgba, src, k) - } - - if err != nil { - return err - } - - if !ok { - draw.Draw(dst, b, dstRgba, b.Min, draw.Src) - } - return nil -} diff --git a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/convolve/convolve_test.go b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/convolve/convolve_test.go deleted file mode 100644 index f34d7afc8..000000000 --- a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/convolve/convolve_test.go +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2011 The Graphics-Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package convolve - -import ( - "code.google.com/p/graphics-go/graphics/graphicstest" - "image" - "reflect" - "testing" - - _ "image/png" -) - -func TestSeparableWeights(t *testing.T) { - sobelXFull := []float64{ - -1, 0, 1, - -2, 0, 2, - -1, 0, 1, - } - sobelXSep := &SeparableKernel{ - X: []float64{-1, 0, +1}, - Y: []float64{1, 2, 1}, - } - w := sobelXSep.Weights() - if !reflect.DeepEqual(w, sobelXFull) { - t.Errorf("got %v want %v", w, sobelXFull) - } -} - -func TestConvolve(t *testing.T) { - kernFull, err := NewKernel([]float64{ - 0, 0, 0, - 1, 1, 1, - 0, 0, 0, - }) - if err != nil { - t.Fatal(err) - } - - kernSep := &SeparableKernel{ - X: []float64{1, 1, 1}, - Y: []float64{0, 1, 0}, - } - - src, err := graphicstest.LoadImage("../../testdata/gopher.png") - if err != nil { - t.Fatal(err) - } - b := src.Bounds() - - sep := image.NewRGBA(b) - if err = Convolve(sep, src, kernSep); err != nil { - t.Fatal(err) - } - - full := image.NewRGBA(b) - Convolve(full, src, kernFull) - - err = graphicstest.ImageWithinTolerance(sep, full, 0x101) - if err != nil { - t.Fatal(err) - } -} - -func TestConvolveNil(t *testing.T) { - if err := Convolve(nil, nil, nil); err != nil { - t.Fatal(err) - } -} - -func TestConvolveEmpty(t *testing.T) { - empty := image.NewRGBA(image.Rect(0, 0, 0, 0)) - if err := Convolve(empty, empty, nil); err != nil { - t.Fatal(err) - } -} diff --git a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/Makefile b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/Makefile deleted file mode 100644 index 0b1c6cb3e..000000000 --- a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/Makefile +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright 2011 The Graphics-Go Authors. All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -include $(GOROOT)/src/Make.inc - -TARG=code.google.com/p/graphics-go/graphics -GOFILES=\ - detect.go\ - doc.go\ - integral.go\ - opencv_parser.go\ - projector.go\ - -include $(GOROOT)/src/Make.pkg diff --git a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/detect.go b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/detect.go deleted file mode 100644 index dde941cbe..000000000 --- a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/detect.go +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright 2011 The Graphics-Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package detect - -import ( - "image" - "math" -) - -// Feature is a Haar-like feature. -type Feature struct { - Rect image.Rectangle - Weight float64 -} - -// Classifier is a set of features with a threshold. -type Classifier struct { - Feature []Feature - Threshold float64 - Left float64 - Right float64 -} - -// CascadeStage is a cascade of classifiers. -type CascadeStage struct { - Classifier []Classifier - Threshold float64 -} - -// Cascade is a degenerate tree of Haar-like classifiers. -type Cascade struct { - Stage []CascadeStage - Size image.Point -} - -// Match returns true if the full image is classified as an object. -func (c *Cascade) Match(m image.Image) bool { - return c.classify(newWindow(m)) -} - -// Find returns a set of areas of m that match the feature cascade c. -func (c *Cascade) Find(m image.Image) []image.Rectangle { - // TODO(crawshaw): Consider de-duping strategies. - matches := []image.Rectangle{} - w := newWindow(m) - - b := m.Bounds() - origScale := c.Size - for s := origScale; s.X < b.Dx() && s.Y < b.Dy(); s = s.Add(s.Div(10)) { - // translate region and classify - tx := image.Pt(s.X/10, 0) - ty := image.Pt(0, s.Y/10) - for r := image.Rect(0, 0, s.X, s.Y).Add(b.Min); r.In(b); r = r.Add(ty) { - for r1 := r; r1.In(b); r1 = r1.Add(tx) { - if c.classify(w.subWindow(r1)) { - matches = append(matches, r1) - } - } - } - } - return matches -} - -type window struct { - mi *integral - miSq *integral - rect image.Rectangle - invArea float64 - stdDev float64 -} - -func (w *window) init() { - w.invArea = 1 / float64(w.rect.Dx()*w.rect.Dy()) - mean := float64(w.mi.sum(w.rect)) * w.invArea - vr := float64(w.miSq.sum(w.rect))*w.invArea - mean*mean - if vr < 0 { - vr = 1 - } - w.stdDev = math.Sqrt(vr) -} - -func newWindow(m image.Image) *window { - mi, miSq := newIntegrals(m) - res := &window{ - mi: mi, - miSq: miSq, - rect: m.Bounds(), - } - res.init() - return res -} - -func (w *window) subWindow(r image.Rectangle) *window { - res := &window{ - mi: w.mi, - miSq: w.miSq, - rect: r, - } - res.init() - return res -} - -func (c *Classifier) classify(w *window, pr *projector) float64 { - s := 0.0 - for _, f := range c.Feature { - s += float64(w.mi.sum(pr.rect(f.Rect))) * f.Weight - } - s *= w.invArea // normalize to maintain scale invariance - if s < c.Threshold*w.stdDev { - return c.Left - } - return c.Right -} - -func (s *CascadeStage) classify(w *window, pr *projector) bool { - sum := 0.0 - for _, c := range s.Classifier { - sum += c.classify(w, pr) - } - return sum >= s.Threshold -} - -func (c *Cascade) classify(w *window) bool { - pr := newProjector(w.rect, image.Rectangle{image.Pt(0, 0), c.Size}) - for _, s := range c.Stage { - if !s.classify(w, pr) { - return false - } - } - return true -} diff --git a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/detect_test.go b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/detect_test.go deleted file mode 100644 index 8a2df113d..000000000 --- a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/detect_test.go +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2011 The Graphics-Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package detect - -import ( - "image" - "image/draw" - "testing" -) - -var ( - c0 = Classifier{ - Feature: []Feature{ - Feature{Rect: image.Rect(0, 0, 3, 4), Weight: 4.0}, - }, - Threshold: 0.2, - Left: 0.8, - Right: 0.2, - } - c1 = Classifier{ - Feature: []Feature{ - Feature{Rect: image.Rect(3, 4, 4, 5), Weight: 4.0}, - }, - Threshold: 0.2, - Left: 0.8, - Right: 0.2, - } - c2 = Classifier{ - Feature: []Feature{ - Feature{Rect: image.Rect(0, 0, 1, 1), Weight: +4.0}, - Feature{Rect: image.Rect(0, 0, 2, 2), Weight: -1.0}, - }, - Threshold: 0.2, - Left: 0.8, - Right: 0.2, - } -) - -func TestClassifier(t *testing.T) { - m := image.NewGray(image.Rect(0, 0, 20, 20)) - b := m.Bounds() - draw.Draw(m, image.Rect(0, 0, 20, 20), image.White, image.ZP, draw.Src) - draw.Draw(m, image.Rect(3, 4, 4, 5), image.Black, image.ZP, draw.Src) - w := newWindow(m) - pr := newProjector(b, b) - - if res := c0.classify(w, pr); res != c0.Right { - t.Errorf("c0 got %f want %f", res, c0.Right) - } - if res := c1.classify(w, pr); res != c1.Left { - t.Errorf("c1 got %f want %f", res, c1.Left) - } - if res := c2.classify(w, pr); res != c1.Left { - t.Errorf("c2 got %f want %f", res, c1.Left) - } -} - -func TestClassifierScale(t *testing.T) { - m := image.NewGray(image.Rect(0, 0, 50, 50)) - b := m.Bounds() - draw.Draw(m, image.Rect(0, 0, 8, 10), image.White, b.Min, draw.Src) - draw.Draw(m, image.Rect(8, 10, 10, 13), image.Black, b.Min, draw.Src) - w := newWindow(m) - pr := newProjector(b, image.Rect(0, 0, 20, 20)) - - if res := c0.classify(w, pr); res != c0.Right { - t.Errorf("scaled c0 got %f want %f", res, c0.Right) - } - if res := c1.classify(w, pr); res != c1.Left { - t.Errorf("scaled c1 got %f want %f", res, c1.Left) - } - if res := c2.classify(w, pr); res != c1.Left { - t.Errorf("scaled c2 got %f want %f", res, c1.Left) - } -} diff --git a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/doc.go b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/doc.go deleted file mode 100644 index a0f4e94cd..000000000 --- a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/doc.go +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2011 The Graphics-Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -/* -Package detect implements an object detector cascade. - -The technique used is a degenerate tree of Haar-like classifiers, commonly -used for face detection. It is described in - - P. Viola, M. Jones. - Rapid Object Detection using a Boosted Cascade of Simple Features, 2001 - IEEE Conference on Computer Vision and Pattern Recognition - -A Cascade can be constructed manually from a set of Classifiers in stages, -or can be loaded from an XML file in the OpenCV format with - - classifier, _, err := detect.ParseOpenCV(r) - -The classifier can be used to determine if a full image is detected as an -object using Detect - - if classifier.Match(m) { - // m is an image of a face. - } - -It is also possible to search an image for occurrences of an object - - objs := classifier.Find(m) -*/ -package detect diff --git a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/integral.go b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/integral.go deleted file mode 100644 index 814ced590..000000000 --- a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/integral.go +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright 2011 The Graphics-Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package detect - -import ( - "image" - "image/draw" -) - -// integral is an image.Image-like structure that stores the cumulative -// sum of the preceding pixels. This allows for O(1) summation of any -// rectangular region within the image. -type integral struct { - // pix holds the cumulative sum of the image's pixels. The pixel at - // (x, y) starts at pix[(y-rect.Min.Y)*stride + (x-rect.Min.X)*1]. - pix []uint64 - stride int - rect image.Rectangle -} - -func (p *integral) at(x, y int) uint64 { - return p.pix[(y-p.rect.Min.Y)*p.stride+(x-p.rect.Min.X)] -} - -func (p *integral) sum(b image.Rectangle) uint64 { - c := p.at(b.Max.X-1, b.Max.Y-1) - inY := b.Min.Y > p.rect.Min.Y - inX := b.Min.X > p.rect.Min.X - if inY && inX { - c += p.at(b.Min.X-1, b.Min.Y-1) - } - if inY { - c -= p.at(b.Max.X-1, b.Min.Y-1) - } - if inX { - c -= p.at(b.Min.X-1, b.Max.Y-1) - } - return c -} - -func (m *integral) integrate() { - b := m.rect - for y := b.Min.Y; y < b.Max.Y; y++ { - for x := b.Min.X; x < b.Max.X; x++ { - c := uint64(0) - if y > b.Min.Y && x > b.Min.X { - c += m.at(x-1, y) - c += m.at(x, y-1) - c -= m.at(x-1, y-1) - } else if y > b.Min.Y { - c += m.at(b.Min.X, y-1) - } else if x > b.Min.X { - c += m.at(x-1, b.Min.Y) - } - m.pix[(y-m.rect.Min.Y)*m.stride+(x-m.rect.Min.X)] += c - } - } -} - -// newIntegrals returns the integral and the squared integral. -func newIntegrals(src image.Image) (*integral, *integral) { - b := src.Bounds() - srcg, ok := src.(*image.Gray) - if !ok { - srcg = image.NewGray(b) - draw.Draw(srcg, b, src, b.Min, draw.Src) - } - - m := integral{ - pix: make([]uint64, b.Max.Y*b.Max.X), - stride: b.Max.X, - rect: b, - } - mSq := integral{ - pix: make([]uint64, b.Max.Y*b.Max.X), - stride: b.Max.X, - rect: b, - } - for y := b.Min.Y; y < b.Max.Y; y++ { - for x := b.Min.X; x < b.Max.X; x++ { - os := (y-b.Min.Y)*srcg.Stride + x - b.Min.X - om := (y-b.Min.Y)*m.stride + x - b.Min.X - c := uint64(srcg.Pix[os]) - m.pix[om] = c - mSq.pix[om] = c * c - } - } - m.integrate() - mSq.integrate() - return &m, &mSq -} diff --git a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/integral_test.go b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/integral_test.go deleted file mode 100644 index 0bc321a4d..000000000 --- a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/integral_test.go +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright 2011 The Graphics-Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package detect - -import ( - "bytes" - "fmt" - "image" - "testing" -) - -type integralTest struct { - x int - y int - src []uint8 - res []uint8 -} - -var integralTests = []integralTest{ - { - 1, 1, - []uint8{0x01}, - []uint8{0x01}, - }, - { - 2, 2, - []uint8{ - 0x01, 0x02, - 0x03, 0x04, - }, - []uint8{ - 0x01, 0x03, - 0x04, 0x0a, - }, - }, - { - 4, 4, - []uint8{ - 0x02, 0x03, 0x00, 0x01, - 0x01, 0x02, 0x01, 0x05, - 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, - }, - []uint8{ - 0x02, 0x05, 0x05, 0x06, - 0x03, 0x08, 0x09, 0x0f, - 0x04, 0x0a, 0x0c, 0x13, - 0x05, 0x0c, 0x0f, 0x17, - }, - }, -} - -func sprintBox(box []byte, width, height int) string { - buf := bytes.NewBuffer(nil) - i := 0 - for y := 0; y < height; y++ { - for x := 0; x < width; x++ { - fmt.Fprintf(buf, " 0x%02x,", box[i]) - i++ - } - buf.WriteByte('\n') - } - return buf.String() -} - -func TestIntegral(t *testing.T) { - for i, oc := range integralTests { - src := &image.Gray{ - Pix: oc.src, - Stride: oc.x, - Rect: image.Rect(0, 0, oc.x, oc.y), - } - dst, _ := newIntegrals(src) - res := make([]byte, len(dst.pix)) - for i, p := range dst.pix { - res[i] = byte(p) - } - - if !bytes.Equal(res, oc.res) { - got := sprintBox(res, oc.x, oc.y) - want := sprintBox(oc.res, oc.x, oc.y) - t.Errorf("%d: got\n%s\n want\n%s", i, got, want) - } - } -} - -func TestIntegralSum(t *testing.T) { - src := &image.Gray{ - Pix: []uint8{ - 0x02, 0x03, 0x00, 0x01, 0x03, - 0x01, 0x02, 0x01, 0x05, 0x05, - 0x01, 0x01, 0x01, 0x01, 0x02, - 0x01, 0x01, 0x01, 0x01, 0x07, - 0x02, 0x01, 0x00, 0x03, 0x01, - }, - Stride: 5, - Rect: image.Rect(0, 0, 5, 5), - } - img, _ := newIntegrals(src) - - type sumTest struct { - rect image.Rectangle - sum uint64 - } - - var sumTests = []sumTest{ - {image.Rect(0, 0, 1, 1), 2}, - {image.Rect(0, 0, 2, 1), 5}, - {image.Rect(0, 0, 1, 3), 4}, - {image.Rect(1, 1, 3, 3), 5}, - {image.Rect(2, 2, 4, 4), 4}, - {image.Rect(4, 3, 5, 5), 8}, - {image.Rect(2, 4, 3, 5), 0}, - } - - for _, st := range sumTests { - s := img.sum(st.rect) - if s != st.sum { - t.Errorf("%v: got %d want %d", st.rect, s, st.sum) - return - } - } -} - -func TestIntegralSubImage(t *testing.T) { - m0 := &image.Gray{ - Pix: []uint8{ - 0x02, 0x03, 0x00, 0x01, 0x03, - 0x01, 0x02, 0x01, 0x05, 0x05, - 0x01, 0x04, 0x01, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x01, 0x07, - 0x02, 0x01, 0x09, 0x03, 0x01, - }, - Stride: 5, - Rect: image.Rect(0, 0, 5, 5), - } - b := image.Rect(1, 1, 4, 4) - m1 := m0.SubImage(b) - mi0, _ := newIntegrals(m0) - mi1, _ := newIntegrals(m1) - - sum0 := mi0.sum(b) - sum1 := mi1.sum(b) - if sum0 != sum1 { - t.Errorf("b got %d want %d", sum0, sum1) - } - - r0 := image.Rect(2, 2, 4, 4) - sum0 = mi0.sum(r0) - sum1 = mi1.sum(r0) - if sum0 != sum1 { - t.Errorf("r0 got %d want %d", sum1, sum0) - } -} diff --git a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/opencv_parser.go b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/opencv_parser.go deleted file mode 100644 index 51ded1a1c..000000000 --- a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/opencv_parser.go +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright 2011 The Graphics-Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package detect - -import ( - "bytes" - "encoding/xml" - "errors" - "fmt" - "image" - "io" - "io/ioutil" - "strconv" - "strings" -) - -type xmlFeature struct { - Rects []string `xml:"grp>feature>rects>grp"` - Tilted int `xml:"grp>feature>tilted"` - Threshold float64 `xml:"grp>threshold"` - Left float64 `xml:"grp>left_val"` - Right float64 `xml:"grp>right_val"` -} - -type xmlStages struct { - Trees []xmlFeature `xml:"trees>grp"` - Stage_threshold float64 `xml:"stage_threshold"` - Parent int `xml:"parent"` - Next int `xml:"next"` -} - -type opencv_storage struct { - Any struct { - XMLName xml.Name - Type string `xml:"type_id,attr"` - Size string `xml:"size"` - Stages []xmlStages `xml:"stages>grp"` - } `xml:",any"` -} - -func buildFeature(r string) (f Feature, err error) { - var x, y, w, h int - var weight float64 - _, err = fmt.Sscanf(r, "%d %d %d %d %f", &x, &y, &w, &h, &weight) - if err != nil { - return - } - f.Rect = image.Rect(x, y, x+w, y+h) - f.Weight = weight - return -} - -func buildCascade(s *opencv_storage) (c *Cascade, name string, err error) { - if s.Any.Type != "opencv-haar-classifier" { - err = fmt.Errorf("got %s want opencv-haar-classifier", s.Any.Type) - return - } - name = s.Any.XMLName.Local - - c = &Cascade{} - sizes := strings.Split(s.Any.Size, " ") - w, err := strconv.Atoi(sizes[0]) - if err != nil { - return nil, "", err - } - h, err := strconv.Atoi(sizes[1]) - if err != nil { - return nil, "", err - } - c.Size = image.Pt(w, h) - c.Stage = []CascadeStage{} - - for _, stage := range s.Any.Stages { - cs := CascadeStage{ - Classifier: []Classifier{}, - Threshold: stage.Stage_threshold, - } - for _, tree := range stage.Trees { - if tree.Tilted != 0 { - err = errors.New("Cascade does not support tilted features") - return - } - - cls := Classifier{ - Feature: []Feature{}, - Threshold: tree.Threshold, - Left: tree.Left, - Right: tree.Right, - } - - for _, rect := range tree.Rects { - f, err := buildFeature(rect) - if err != nil { - return nil, "", err - } - cls.Feature = append(cls.Feature, f) - } - - cs.Classifier = append(cs.Classifier, cls) - } - c.Stage = append(c.Stage, cs) - } - - return -} - -// ParseOpenCV produces a detection Cascade from an OpenCV XML file. -func ParseOpenCV(r io.Reader) (cascade *Cascade, name string, err error) { - // BUG(crawshaw): tag-based parsing doesn't seem to work with <_> - buf, err := ioutil.ReadAll(r) - if err != nil { - return - } - buf = bytes.Replace(buf, []byte("<_>"), []byte("<grp>"), -1) - buf = bytes.Replace(buf, []byte("</_>"), []byte("</grp>"), -1) - - s := &opencv_storage{} - err = xml.Unmarshal(buf, s) - if err != nil { - return - } - return buildCascade(s) -} diff --git a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/opencv_parser_test.go b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/opencv_parser_test.go deleted file mode 100644 index 343390499..000000000 --- a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/opencv_parser_test.go +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2011 The Graphics-Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package detect - -import ( - "image" - "os" - "reflect" - "testing" -) - -var ( - classifier0 = Classifier{ - Feature: []Feature{ - Feature{Rect: image.Rect(0, 0, 3, 4), Weight: -1}, - Feature{Rect: image.Rect(3, 4, 5, 6), Weight: 3.1}, - }, - Threshold: 0.03, - Left: 0.01, - Right: 0.8, - } - classifier1 = Classifier{ - Feature: []Feature{ - Feature{Rect: image.Rect(3, 7, 17, 11), Weight: -3.2}, - Feature{Rect: image.Rect(3, 9, 17, 11), Weight: 2.}, - }, - Threshold: 0.11, - Left: 0.03, - Right: 0.83, - } - classifier2 = Classifier{ - Feature: []Feature{ - Feature{Rect: image.Rect(1, 1, 3, 3), Weight: -1.}, - Feature{Rect: image.Rect(3, 3, 5, 5), Weight: 2.5}, - }, - Threshold: 0.07, - Left: 0.2, - Right: 0.4, - } - cascade = Cascade{ - Stage: []CascadeStage{ - CascadeStage{ - Classifier: []Classifier{classifier0, classifier1}, - Threshold: 0.82, - }, - CascadeStage{ - Classifier: []Classifier{classifier2}, - Threshold: 0.22, - }, - }, - Size: image.Pt(20, 20), - } -) - -func TestParseOpenCV(t *testing.T) { - file, err := os.Open("../../testdata/opencv.xml") - if err != nil { - t.Fatal(err) - } - defer file.Close() - - cascadeFile, name, err := ParseOpenCV(file) - if err != nil { - t.Fatal(err) - } - if name != "name_of_cascade" { - t.Fatalf("name: got %s want name_of_cascade", name) - } - - if !reflect.DeepEqual(cascade, *cascadeFile) { - t.Errorf("got\n %v want\n %v", *cascadeFile, cascade) - } -} diff --git a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/projector.go b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/projector.go deleted file mode 100644 index 1ebd6db59..000000000 --- a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/projector.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2011 The Graphics-Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package detect - -import ( - "image" -) - -// projector allows projecting from a source Rectangle onto a target Rectangle. -type projector struct { - // rx, ry is the scaling factor. - rx, ry float64 - // dx, dy is the translation factor. - dx, dy float64 - // r is the clipping region of the target. - r image.Rectangle -} - -// newProjector creates a Projector with source src and target dst. -func newProjector(dst image.Rectangle, src image.Rectangle) *projector { - return &projector{ - rx: float64(dst.Dx()) / float64(src.Dx()), - ry: float64(dst.Dy()) / float64(src.Dy()), - dx: float64(dst.Min.X - src.Min.X), - dy: float64(dst.Min.Y - src.Min.Y), - r: dst, - } -} - -// pt projects p from the source rectangle onto the target rectangle. -func (s *projector) pt(p image.Point) image.Point { - return image.Point{ - clamp(s.rx*float64(p.X)+s.dx, s.r.Min.X, s.r.Max.X), - clamp(s.ry*float64(p.Y)+s.dy, s.r.Min.Y, s.r.Max.Y), - } -} - -// rect projects r from the source rectangle onto the target rectangle. -func (s *projector) rect(r image.Rectangle) image.Rectangle { - return image.Rectangle{s.pt(r.Min), s.pt(r.Max)} -} - -// clamp rounds and clamps o to the integer range [x0, x1]. -func clamp(o float64, x0, x1 int) int { - x := int(o + 0.5) - if x < x0 { - return x0 - } - if x > x1 { - return x1 - } - return x -} diff --git a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/projector_test.go b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/projector_test.go deleted file mode 100644 index c6d0b0cd5..000000000 --- a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/projector_test.go +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2011 The Graphics-Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package detect - -import ( - "image" - "reflect" - "testing" -) - -type projectorTest struct { - dst image.Rectangle - src image.Rectangle - pdst image.Rectangle - psrc image.Rectangle -} - -var projectorTests = []projectorTest{ - { - image.Rect(0, 0, 6, 6), - image.Rect(0, 0, 2, 2), - image.Rect(0, 0, 6, 6), - image.Rect(0, 0, 2, 2), - }, - { - image.Rect(0, 0, 6, 6), - image.Rect(0, 0, 2, 2), - image.Rect(3, 3, 6, 6), - image.Rect(1, 1, 2, 2), - }, - { - image.Rect(30, 30, 40, 40), - image.Rect(10, 10, 20, 20), - image.Rect(32, 33, 34, 37), - image.Rect(12, 13, 14, 17), - }, -} - -func TestProjector(t *testing.T) { - for i, tt := range projectorTests { - pr := newProjector(tt.dst, tt.src) - res := pr.rect(tt.psrc) - if !reflect.DeepEqual(res, tt.pdst) { - t.Errorf("%d: got %v want %v", i, res, tt.pdst) - } - } -} diff --git a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/graphicstest/Makefile b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/graphicstest/Makefile deleted file mode 100644 index 7bfdf22d8..000000000 --- a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/graphicstest/Makefile +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright 2011 The Graphics-Go Authors. All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -include $(GOROOT)/src/Make.inc - -TARG=code.google.com/p/graphics-go/graphics/graphicstest -GOFILES=\ - graphicstest.go\ - -include $(GOROOT)/src/Make.pkg diff --git a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/graphicstest/graphicstest.go b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/graphicstest/graphicstest.go deleted file mode 100644 index ceb3a974d..000000000 --- a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/graphicstest/graphicstest.go +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright 2011 The Graphics-Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package graphicstest - -import ( - "bytes" - "errors" - "fmt" - "image" - "image/color" - "os" -) - -// LoadImage decodes an image from a file. -func LoadImage(path string) (img image.Image, err error) { - file, err := os.Open(path) - if err != nil { - return - } - defer file.Close() - img, _, err = image.Decode(file) - return -} - -func delta(u0, u1 uint32) int { - d := int(u0) - int(u1) - if d < 0 { - return -d - } - return d -} - -func withinTolerance(c0, c1 color.Color, tol int) bool { - r0, g0, b0, a0 := c0.RGBA() - r1, g1, b1, a1 := c1.RGBA() - r := delta(r0, r1) - g := delta(g0, g1) - b := delta(b0, b1) - a := delta(a0, a1) - return r <= tol && g <= tol && b <= tol && a <= tol -} - -// ImageWithinTolerance checks that each pixel varies by no more than tol. -func ImageWithinTolerance(m0, m1 image.Image, tol int) error { - b0 := m0.Bounds() - b1 := m1.Bounds() - if !b0.Eq(b1) { - return errors.New(fmt.Sprintf("got bounds %v want %v", b0, b1)) - } - - for y := b0.Min.Y; y < b0.Max.Y; y++ { - for x := b0.Min.X; x < b0.Max.X; x++ { - c0 := m0.At(x, y) - c1 := m1.At(x, y) - if !withinTolerance(c0, c1, tol) { - e := fmt.Sprintf("got %v want %v at (%d, %d)", c0, c1, x, y) - return errors.New(e) - } - } - } - return nil -} - -// SprintBox pretty prints the array as a hexidecimal matrix. -func SprintBox(box []byte, width, height int) string { - buf := bytes.NewBuffer(nil) - i := 0 - for y := 0; y < height; y++ { - for x := 0; x < width; x++ { - fmt.Fprintf(buf, " 0x%02x,", box[i]) - i++ - } - buf.WriteByte('\n') - } - return buf.String() -} - -// SprintImageR pretty prints the red channel of src. It looks like SprintBox. -func SprintImageR(src *image.RGBA) string { - w, h := src.Rect.Dx(), src.Rect.Dy() - i := 0 - box := make([]byte, w*h) - for y := src.Rect.Min.Y; y < src.Rect.Max.Y; y++ { - for x := src.Rect.Min.X; x < src.Rect.Max.X; x++ { - off := (y-src.Rect.Min.Y)*src.Stride + (x-src.Rect.Min.X)*4 - box[i] = src.Pix[off] - i++ - } - } - return SprintBox(box, w, h) -} - -// MakeRGBA returns an image with R, G, B taken from src. -func MakeRGBA(src []uint8, width int) *image.RGBA { - b := image.Rect(0, 0, width, len(src)/width) - ret := image.NewRGBA(b) - i := 0 - for y := b.Min.Y; y < b.Max.Y; y++ { - for x := b.Min.X; x < b.Max.X; x++ { - ret.SetRGBA(x, y, color.RGBA{ - R: src[i], - G: src[i], - B: src[i], - A: 0xff, - }) - i++ - } - } - return ret -} diff --git a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/interp/Makefile b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/interp/Makefile deleted file mode 100644 index 4d8f524fb..000000000 --- a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/interp/Makefile +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2012 The Graphics-Go Authors. All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -include $(GOROOT)/src/Make.inc - -TARG=code.google.com/p/graphics-go/graphics/interp -GOFILES=\ - bilinear.go\ - doc.go\ - interp.go\ - -include $(GOROOT)/src/Make.pkg diff --git a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/interp/bilinear.go b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/interp/bilinear.go deleted file mode 100644 index e18321a15..000000000 --- a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/interp/bilinear.go +++ /dev/null @@ -1,206 +0,0 @@ -// Copyright 2012 The Graphics-Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package interp - -import ( - "image" - "image/color" - "math" -) - -// Bilinear implements bilinear interpolation. -var Bilinear Interp = bilinear{} - -type bilinear struct{} - -func (i bilinear) Interp(src image.Image, x, y float64) color.Color { - if src, ok := src.(*image.RGBA); ok { - return i.RGBA(src, x, y) - } - return bilinearGeneral(src, x, y) -} - -func bilinearGeneral(src image.Image, x, y float64) color.Color { - p := findLinearSrc(src.Bounds(), x, y) - var fr, fg, fb, fa float64 - var r, g, b, a uint32 - - r, g, b, a = src.At(p.low.X, p.low.Y).RGBA() - fr += float64(r) * p.frac00 - fg += float64(g) * p.frac00 - fb += float64(b) * p.frac00 - fa += float64(a) * p.frac00 - - r, g, b, a = src.At(p.high.X, p.low.Y).RGBA() - fr += float64(r) * p.frac01 - fg += float64(g) * p.frac01 - fb += float64(b) * p.frac01 - fa += float64(a) * p.frac01 - - r, g, b, a = src.At(p.low.X, p.high.Y).RGBA() - fr += float64(r) * p.frac10 - fg += float64(g) * p.frac10 - fb += float64(b) * p.frac10 - fa += float64(a) * p.frac10 - - r, g, b, a = src.At(p.high.X, p.high.Y).RGBA() - fr += float64(r) * p.frac11 - fg += float64(g) * p.frac11 - fb += float64(b) * p.frac11 - fa += float64(a) * p.frac11 - - var c color.RGBA64 - c.R = uint16(fr + 0.5) - c.G = uint16(fg + 0.5) - c.B = uint16(fb + 0.5) - c.A = uint16(fa + 0.5) - return c -} - -func (bilinear) RGBA(src *image.RGBA, x, y float64) color.RGBA { - p := findLinearSrc(src.Bounds(), x, y) - - // Array offsets for the surrounding pixels. - off00 := offRGBA(src, p.low.X, p.low.Y) - off01 := offRGBA(src, p.high.X, p.low.Y) - off10 := offRGBA(src, p.low.X, p.high.Y) - off11 := offRGBA(src, p.high.X, p.high.Y) - - var fr, fg, fb, fa float64 - - fr += float64(src.Pix[off00+0]) * p.frac00 - fg += float64(src.Pix[off00+1]) * p.frac00 - fb += float64(src.Pix[off00+2]) * p.frac00 - fa += float64(src.Pix[off00+3]) * p.frac00 - - fr += float64(src.Pix[off01+0]) * p.frac01 - fg += float64(src.Pix[off01+1]) * p.frac01 - fb += float64(src.Pix[off01+2]) * p.frac01 - fa += float64(src.Pix[off01+3]) * p.frac01 - - fr += float64(src.Pix[off10+0]) * p.frac10 - fg += float64(src.Pix[off10+1]) * p.frac10 - fb += float64(src.Pix[off10+2]) * p.frac10 - fa += float64(src.Pix[off10+3]) * p.frac10 - - fr += float64(src.Pix[off11+0]) * p.frac11 - fg += float64(src.Pix[off11+1]) * p.frac11 - fb += float64(src.Pix[off11+2]) * p.frac11 - fa += float64(src.Pix[off11+3]) * p.frac11 - - var c color.RGBA - c.R = uint8(fr + 0.5) - c.G = uint8(fg + 0.5) - c.B = uint8(fb + 0.5) - c.A = uint8(fa + 0.5) - return c -} - -func (bilinear) Gray(src *image.Gray, x, y float64) color.Gray { - p := findLinearSrc(src.Bounds(), x, y) - - // Array offsets for the surrounding pixels. - off00 := offGray(src, p.low.X, p.low.Y) - off01 := offGray(src, p.high.X, p.low.Y) - off10 := offGray(src, p.low.X, p.high.Y) - off11 := offGray(src, p.high.X, p.high.Y) - - var fc float64 - fc += float64(src.Pix[off00]) * p.frac00 - fc += float64(src.Pix[off01]) * p.frac01 - fc += float64(src.Pix[off10]) * p.frac10 - fc += float64(src.Pix[off11]) * p.frac11 - - var c color.Gray - c.Y = uint8(fc + 0.5) - return c -} - -type bilinearSrc struct { - // Top-left and bottom-right interpolation sources - low, high image.Point - // Fraction of each pixel to take. The 0 suffix indicates - // top/left, and the 1 suffix indicates bottom/right. - frac00, frac01, frac10, frac11 float64 -} - -func findLinearSrc(b image.Rectangle, sx, sy float64) bilinearSrc { - maxX := float64(b.Max.X) - maxY := float64(b.Max.Y) - minX := float64(b.Min.X) - minY := float64(b.Min.Y) - lowX := math.Floor(sx - 0.5) - lowY := math.Floor(sy - 0.5) - if lowX < minX { - lowX = minX - } - if lowY < minY { - lowY = minY - } - - highX := math.Ceil(sx - 0.5) - highY := math.Ceil(sy - 0.5) - if highX >= maxX { - highX = maxX - 1 - } - if highY >= maxY { - highY = maxY - 1 - } - - // In the variables below, the 0 suffix indicates top/left, and the - // 1 suffix indicates bottom/right. - - // Center of each surrounding pixel. - x00 := lowX + 0.5 - y00 := lowY + 0.5 - x01 := highX + 0.5 - y01 := lowY + 0.5 - x10 := lowX + 0.5 - y10 := highY + 0.5 - x11 := highX + 0.5 - y11 := highY + 0.5 - - p := bilinearSrc{ - low: image.Pt(int(lowX), int(lowY)), - high: image.Pt(int(highX), int(highY)), - } - - // Literally, edge cases. If we are close enough to the edge of - // the image, curtail the interpolation sources. - if lowX == highX && lowY == highY { - p.frac00 = 1.0 - } else if sy-minY <= 0.5 && sx-minX <= 0.5 { - p.frac00 = 1.0 - } else if maxY-sy <= 0.5 && maxX-sx <= 0.5 { - p.frac11 = 1.0 - } else if sy-minY <= 0.5 || lowY == highY { - p.frac00 = x01 - sx - p.frac01 = sx - x00 - } else if sx-minX <= 0.5 || lowX == highX { - p.frac00 = y10 - sy - p.frac10 = sy - y00 - } else if maxY-sy <= 0.5 { - p.frac10 = x11 - sx - p.frac11 = sx - x10 - } else if maxX-sx <= 0.5 { - p.frac01 = y11 - sy - p.frac11 = sy - y01 - } else { - p.frac00 = (x01 - sx) * (y10 - sy) - p.frac01 = (sx - x00) * (y11 - sy) - p.frac10 = (x11 - sx) * (sy - y00) - p.frac11 = (sx - x10) * (sy - y01) - } - - return p -} - -// TODO(crawshaw): When we have inlining, consider func (p *RGBA) Off(x, y) int -func offRGBA(src *image.RGBA, x, y int) int { - return (y-src.Rect.Min.Y)*src.Stride + (x-src.Rect.Min.X)*4 -} -func offGray(src *image.Gray, x, y int) int { - return (y-src.Rect.Min.Y)*src.Stride + (x - src.Rect.Min.X) -} diff --git a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/interp/bilinear_test.go b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/interp/bilinear_test.go deleted file mode 100644 index 242d70546..000000000 --- a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/interp/bilinear_test.go +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright 2012 The Graphics-Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package interp - -import ( - "image" - "image/color" - "testing" -) - -type interpTest struct { - desc string - src []uint8 - srcWidth int - x, y float64 - expect uint8 -} - -func (p *interpTest) newSrc() *image.RGBA { - b := image.Rect(0, 0, p.srcWidth, len(p.src)/p.srcWidth) - src := image.NewRGBA(b) - i := 0 - for y := b.Min.Y; y < b.Max.Y; y++ { - for x := b.Min.X; x < b.Max.X; x++ { - src.SetRGBA(x, y, color.RGBA{ - R: p.src[i], - G: p.src[i], - B: p.src[i], - A: 0xff, - }) - i++ - } - } - return src -} - -var interpTests = []interpTest{ - { - desc: "center of a single white pixel should match that pixel", - src: []uint8{0x00}, - srcWidth: 1, - x: 0.5, - y: 0.5, - expect: 0x00, - }, - { - desc: "middle of a square is equally weighted", - src: []uint8{ - 0x00, 0xff, - 0xff, 0x00, - }, - srcWidth: 2, - x: 1.0, - y: 1.0, - expect: 0x80, - }, - { - desc: "center of a pixel is just that pixel", - src: []uint8{ - 0x00, 0xff, - 0xff, 0x00, - }, - srcWidth: 2, - x: 1.5, - y: 0.5, - expect: 0xff, - }, - { - desc: "asymmetry abounds", - src: []uint8{ - 0xaa, 0x11, 0x55, - 0xff, 0x95, 0xdd, - }, - srcWidth: 3, - x: 2.0, - y: 1.0, - expect: 0x76, // (0x11 + 0x55 + 0x95 + 0xdd) / 4 - }, -} - -func TestBilinearRGBA(t *testing.T) { - for _, p := range interpTests { - src := p.newSrc() - - // Fast path. - c := Bilinear.(RGBA).RGBA(src, p.x, p.y) - if c.R != c.G || c.R != c.B || c.A != 0xff { - t.Errorf("expect channels to match, got %v", c) - continue - } - if c.R != p.expect { - t.Errorf("%s: got 0x%02x want 0x%02x", p.desc, c.R, p.expect) - continue - } - - // Standard Interp should use the fast path. - cStd := Bilinear.Interp(src, p.x, p.y) - if cStd != c { - t.Errorf("%s: standard mismatch got %v want %v", p.desc, cStd, c) - continue - } - - // General case should match the fast path. - cGen := color.RGBAModel.Convert(bilinearGeneral(src, p.x, p.y)) - r0, g0, b0, a0 := c.RGBA() - r1, g1, b1, a1 := cGen.RGBA() - if r0 != r1 || g0 != g1 || b0 != b1 || a0 != a1 { - t.Errorf("%s: general case mismatch got %v want %v", p.desc, c, cGen) - continue - } - } -} - -func TestBilinearSubImage(t *testing.T) { - b0 := image.Rect(0, 0, 4, 4) - src0 := image.NewRGBA(b0) - b1 := image.Rect(1, 1, 3, 3) - src1 := src0.SubImage(b1).(*image.RGBA) - src1.Set(1, 1, color.RGBA{0x11, 0, 0, 0xff}) - src1.Set(2, 1, color.RGBA{0x22, 0, 0, 0xff}) - src1.Set(1, 2, color.RGBA{0x33, 0, 0, 0xff}) - src1.Set(2, 2, color.RGBA{0x44, 0, 0, 0xff}) - - tests := []struct { - x, y float64 - want uint8 - }{ - {1, 1, 0x11}, - {3, 1, 0x22}, - {1, 3, 0x33}, - {3, 3, 0x44}, - {2, 2, 0x2b}, - } - - for _, p := range tests { - c := Bilinear.(RGBA).RGBA(src1, p.x, p.y) - if c.R != p.want { - t.Errorf("(%.0f, %.0f): got 0x%02x want 0x%02x", p.x, p.y, c.R, p.want) - } - } -} diff --git a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/interp/doc.go b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/interp/doc.go deleted file mode 100644 index b115534cc..000000000 --- a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/interp/doc.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2012 The Graphics-Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -/* -Package interp implements image interpolation. - -An interpolator provides the Interp interface, which can be used -to interpolate a pixel: - - c := interp.Bilinear.Interp(src, 1.2, 1.8) - -To interpolate a large number of RGBA or Gray pixels, an implementation -may provide a fast-path by implementing the RGBA or Gray interfaces. - - i1, ok := i.(interp.RGBA) - if ok { - c := i1.RGBA(src, 1.2, 1.8) - // use c.R, c.G, etc - return - } - c := i.Interp(src, 1.2, 1.8) - // use generic color.Color -*/ -package interp diff --git a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/interp/interp.go b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/interp/interp.go deleted file mode 100644 index 560637d4a..000000000 --- a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/interp/interp.go +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2012 The Graphics-Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package interp - -import ( - "image" - "image/color" -) - -// Interp interpolates an image's color at fractional co-ordinates. -type Interp interface { - // Interp interpolates (x, y). - Interp(src image.Image, x, y float64) color.Color -} - -// RGBA is a fast-path interpolation implementation for image.RGBA. -// It is common for an Interp to also implement RGBA. -type RGBA interface { - // RGBA interpolates (x, y). - RGBA(src *image.RGBA, x, y float64) color.RGBA -} - -// Gray is a fast-path interpolation implementation for image.Gray. -type Gray interface { - // Gray interpolates (x, y). - Gray(src *image.Gray, x, y float64) color.Gray -} diff --git a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/rotate.go b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/rotate.go deleted file mode 100644 index 62bde1a08..000000000 --- a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/rotate.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2011 The Graphics-Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package graphics - -import ( - "code.google.com/p/graphics-go/graphics/interp" - "errors" - "image" - "image/draw" -) - -// RotateOptions are the rotation parameters. -// Angle is the angle, in radians, to rotate the image clockwise. -type RotateOptions struct { - Angle float64 -} - -// Rotate produces a rotated version of src, drawn onto dst. -func Rotate(dst draw.Image, src image.Image, opt *RotateOptions) error { - if dst == nil { - return errors.New("graphics: dst is nil") - } - if src == nil { - return errors.New("graphics: src is nil") - } - - angle := 0.0 - if opt != nil { - angle = opt.Angle - } - - return I.Rotate(angle).TransformCenter(dst, src, interp.Bilinear) -} diff --git a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/rotate_test.go b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/rotate_test.go deleted file mode 100644 index bfc532a0a..000000000 --- a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/rotate_test.go +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright 2011 The Graphics-Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package graphics - -import ( - "code.google.com/p/graphics-go/graphics/graphicstest" - "image" - "math" - "testing" - - _ "image/png" -) - -var rotateOneColorTests = []transformOneColorTest{ - { - "onepixel-onequarter", 1, 1, 1, 1, - &RotateOptions{math.Pi / 2}, - []uint8{0xff}, - []uint8{0xff}, - }, - { - "onepixel-partial", 1, 1, 1, 1, - &RotateOptions{math.Pi * 2.0 / 3.0}, - []uint8{0xff}, - []uint8{0xff}, - }, - { - "onepixel-complete", 1, 1, 1, 1, - &RotateOptions{2 * math.Pi}, - []uint8{0xff}, - []uint8{0xff}, - }, - { - "even-onequarter", 2, 2, 2, 2, - &RotateOptions{math.Pi / 2.0}, - []uint8{ - 0xff, 0x00, - 0x00, 0xff, - }, - []uint8{ - 0x00, 0xff, - 0xff, 0x00, - }, - }, - { - "even-complete", 2, 2, 2, 2, - &RotateOptions{2.0 * math.Pi}, - []uint8{ - 0xff, 0x00, - 0x00, 0xff, - }, - []uint8{ - 0xff, 0x00, - 0x00, 0xff, - }, - }, - { - "line-partial", 3, 3, 3, 3, - &RotateOptions{math.Pi * 1.0 / 3.0}, - []uint8{ - 0x00, 0x00, 0x00, - 0xff, 0xff, 0xff, - 0x00, 0x00, 0x00, - }, - []uint8{ - 0xa2, 0x80, 0x00, - 0x22, 0xff, 0x22, - 0x00, 0x80, 0xa2, - }, - }, - { - "line-offset-partial", 3, 3, 3, 3, - &RotateOptions{math.Pi * 3 / 2}, - []uint8{ - 0x00, 0x00, 0x00, - 0x00, 0xff, 0xff, - 0x00, 0x00, 0x00, - }, - []uint8{ - 0x00, 0xff, 0x00, - 0x00, 0xff, 0x00, - 0x00, 0x00, 0x00, - }, - }, - { - "dot-partial", 4, 4, 4, 4, - &RotateOptions{math.Pi}, - []uint8{ - 0x00, 0x00, 0x00, 0x00, - 0x00, 0xff, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - }, - []uint8{ - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0xff, 0x00, - 0x00, 0x00, 0x00, 0x00, - }, - }, -} - -func TestRotateOneColor(t *testing.T) { - for _, oc := range rotateOneColorTests { - src := oc.newSrc() - dst := oc.newDst() - - if err := Rotate(dst, src, oc.opt.(*RotateOptions)); err != nil { - t.Errorf("rotate %s: %v", oc.desc, err) - continue - } - if !checkTransformTest(t, &oc, dst) { - continue - } - } -} - -func TestRotateEmpty(t *testing.T) { - empty := image.NewRGBA(image.Rect(0, 0, 0, 0)) - if err := Rotate(empty, empty, nil); err != nil { - t.Fatal(err) - } -} - -func TestRotateGopherSide(t *testing.T) { - src, err := graphicstest.LoadImage("../testdata/gopher.png") - if err != nil { - t.Fatal(err) - } - - srcb := src.Bounds() - dst := image.NewRGBA(image.Rect(0, 0, srcb.Dy(), srcb.Dx())) - if err := Rotate(dst, src, &RotateOptions{math.Pi / 2.0}); err != nil { - t.Fatal(err) - } - - cmp, err := graphicstest.LoadImage("../testdata/gopher-rotate-side.png") - if err != nil { - t.Fatal(err) - } - err = graphicstest.ImageWithinTolerance(dst, cmp, 0x101) - if err != nil { - t.Fatal(err) - } -} - -func TestRotateGopherPartial(t *testing.T) { - src, err := graphicstest.LoadImage("../testdata/gopher.png") - if err != nil { - t.Fatal(err) - } - - srcb := src.Bounds() - dst := image.NewRGBA(image.Rect(0, 0, srcb.Dx(), srcb.Dy())) - if err := Rotate(dst, src, &RotateOptions{math.Pi / 3.0}); err != nil { - t.Fatal(err) - } - - cmp, err := graphicstest.LoadImage("../testdata/gopher-rotate-partial.png") - if err != nil { - t.Fatal(err) - } - err = graphicstest.ImageWithinTolerance(dst, cmp, 0x101) - if err != nil { - t.Fatal(err) - } -} diff --git a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/scale.go b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/scale.go deleted file mode 100644 index 7a7fe9696..000000000 --- a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/scale.go +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2011 The Graphics-Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package graphics - -import ( - "code.google.com/p/graphics-go/graphics/interp" - "errors" - "image" - "image/draw" -) - -// Scale produces a scaled version of the image using bilinear interpolation. -func Scale(dst draw.Image, src image.Image) error { - if dst == nil { - return errors.New("graphics: dst is nil") - } - if src == nil { - return errors.New("graphics: src is nil") - } - - b := dst.Bounds() - srcb := src.Bounds() - if b.Empty() || srcb.Empty() { - return nil - } - sx := float64(b.Dx()) / float64(srcb.Dx()) - sy := float64(b.Dy()) / float64(srcb.Dy()) - return I.Scale(sx, sy).Transform(dst, src, interp.Bilinear) -} diff --git a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/scale_test.go b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/scale_test.go deleted file mode 100644 index 9c2468f11..000000000 --- a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/scale_test.go +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright 2011 The Graphics-Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package graphics - -import ( - "code.google.com/p/graphics-go/graphics/graphicstest" - "image" - "testing" - - _ "image/png" -) - -var scaleOneColorTests = []transformOneColorTest{ - { - "down-half", - 1, 1, - 2, 2, - nil, - []uint8{ - 0x80, 0x00, - 0x00, 0x80, - }, - []uint8{ - 0x40, - }, - }, - { - "up-double", - 4, 4, - 2, 2, - nil, - []uint8{ - 0x80, 0x00, - 0x00, 0x80, - }, - []uint8{ - 0x80, 0x60, 0x20, 0x00, - 0x60, 0x50, 0x30, 0x20, - 0x20, 0x30, 0x50, 0x60, - 0x00, 0x20, 0x60, 0x80, - }, - }, - { - "up-doublewidth", - 4, 2, - 2, 2, - nil, - []uint8{ - 0x80, 0x00, - 0x00, 0x80, - }, - []uint8{ - 0x80, 0x60, 0x20, 0x00, - 0x00, 0x20, 0x60, 0x80, - }, - }, - { - "up-doubleheight", - 2, 4, - 2, 2, - nil, - []uint8{ - 0x80, 0x00, - 0x00, 0x80, - }, - []uint8{ - 0x80, 0x00, - 0x60, 0x20, - 0x20, 0x60, - 0x00, 0x80, - }, - }, - { - "up-partial", - 3, 3, - 2, 2, - nil, - []uint8{ - 0x80, 0x00, - 0x00, 0x80, - }, - []uint8{ - 0x80, 0x40, 0x00, - 0x40, 0x40, 0x40, - 0x00, 0x40, 0x80, - }, - }, -} - -func TestScaleOneColor(t *testing.T) { - for _, oc := range scaleOneColorTests { - dst := oc.newDst() - src := oc.newSrc() - if err := Scale(dst, src); err != nil { - t.Errorf("scale %s: %v", oc.desc, err) - continue - } - - if !checkTransformTest(t, &oc, dst) { - continue - } - } -} - -func TestScaleEmpty(t *testing.T) { - empty := image.NewRGBA(image.Rect(0, 0, 0, 0)) - if err := Scale(empty, empty); err != nil { - t.Fatal(err) - } -} - -func TestScaleGopher(t *testing.T) { - dst := image.NewRGBA(image.Rect(0, 0, 100, 150)) - - src, err := graphicstest.LoadImage("../testdata/gopher.png") - if err != nil { - t.Error(err) - return - } - - // Down-sample. - if err := Scale(dst, src); err != nil { - t.Fatal(err) - } - cmp, err := graphicstest.LoadImage("../testdata/gopher-100x150.png") - if err != nil { - t.Error(err) - return - } - err = graphicstest.ImageWithinTolerance(dst, cmp, 0) - if err != nil { - t.Error(err) - return - } - - // Up-sample. - dst = image.NewRGBA(image.Rect(0, 0, 500, 750)) - if err := Scale(dst, src); err != nil { - t.Fatal(err) - } - cmp, err = graphicstest.LoadImage("../testdata/gopher-500x750.png") - if err != nil { - t.Error(err) - return - } - err = graphicstest.ImageWithinTolerance(dst, cmp, 0) - if err != nil { - t.Error(err) - return - } -} diff --git a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/shared_test.go b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/shared_test.go deleted file mode 100644 index e1cd21fb3..000000000 --- a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/shared_test.go +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2011 The Graphics-Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package graphics - -import ( - "bytes" - "code.google.com/p/graphics-go/graphics/graphicstest" - "image" - "image/color" - "testing" -) - -type transformOneColorTest struct { - desc string - dstWidth int - dstHeight int - srcWidth int - srcHeight int - opt interface{} - src []uint8 - res []uint8 -} - -func (oc *transformOneColorTest) newSrc() *image.RGBA { - b := image.Rect(0, 0, oc.srcWidth, oc.srcHeight) - src := image.NewRGBA(b) - i := 0 - for y := b.Min.Y; y < b.Max.Y; y++ { - for x := b.Min.X; x < b.Max.X; x++ { - src.SetRGBA(x, y, color.RGBA{ - R: oc.src[i], - G: oc.src[i], - B: oc.src[i], - A: oc.src[i], - }) - i++ - } - } - return src -} - -func (oc *transformOneColorTest) newDst() *image.RGBA { - return image.NewRGBA(image.Rect(0, 0, oc.dstWidth, oc.dstHeight)) -} - -func checkTransformTest(t *testing.T, oc *transformOneColorTest, dst *image.RGBA) bool { - for ch := 0; ch < 4; ch++ { - i := 0 - res := make([]byte, len(oc.res)) - for y := 0; y < oc.dstHeight; y++ { - for x := 0; x < oc.dstWidth; x++ { - off := (y-dst.Rect.Min.Y)*dst.Stride + (x-dst.Rect.Min.X)*4 - res[i] = dst.Pix[off+ch] - i++ - } - } - - if !bytes.Equal(res, oc.res) { - got := graphicstest.SprintBox(res, oc.dstWidth, oc.dstHeight) - want := graphicstest.SprintBox(oc.res, oc.dstWidth, oc.dstHeight) - t.Errorf("%s: ch=%d\n got\n%s\n want\n%s", oc.desc, ch, got, want) - return false - } - } - - return true -} diff --git a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/thumbnail.go b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/thumbnail.go deleted file mode 100644 index d3ad7e8f7..000000000 --- a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/thumbnail.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2011 The Graphics-Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package graphics - -import ( - "image" - "image/draw" -) - -// Thumbnail scales and crops src so it fits in dst. -func Thumbnail(dst draw.Image, src image.Image) error { - // Scale down src in the dimension that is closer to dst. - sb := src.Bounds() - db := dst.Bounds() - rx := float64(sb.Dx()) / float64(db.Dx()) - ry := float64(sb.Dy()) / float64(db.Dy()) - var b image.Rectangle - if rx < ry { - b = image.Rect(0, 0, db.Dx(), int(float64(sb.Dy())/rx)) - } else { - b = image.Rect(0, 0, int(float64(sb.Dx())/ry), db.Dy()) - } - - buf := image.NewRGBA(b) - if err := Scale(buf, src); err != nil { - return err - } - - // Crop. - // TODO(crawshaw): improve on center-alignment. - var pt image.Point - if rx < ry { - pt.Y = (b.Dy() - db.Dy()) / 2 - } else { - pt.X = (b.Dx() - db.Dx()) / 2 - } - draw.Draw(dst, db, buf, pt, draw.Src) - return nil -} diff --git a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/thumbnail_test.go b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/thumbnail_test.go deleted file mode 100644 index d12659f17..000000000 --- a/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/thumbnail_test.go +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2011 The Graphics-Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package graphics - -import ( - "code.google.com/p/graphics-go/graphics/graphicstest" - "image" - "testing" - - _ "image/png" -) - -func TestThumbnailGopher(t *testing.T) { - dst := image.NewRGBA(image.Rect(0, 0, 80, 80)) - - src, err := graphicstest.LoadImage("../testdata/gopher.png") - if err != nil { - t.Fatal(err) - } - if err := Thumbnail(dst, src); err != nil { - t.Fatal(err) - } - cmp, err := graphicstest.LoadImage("../testdata/gopher-thumb-80x80.png") - if err != nil { - t.Fatal(err) - } - err = graphicstest.ImageWithinTolerance(dst, cmp, 0) - if err != nil { - t.Error(err) - } -} - -func TestThumbnailLongGopher(t *testing.T) { - dst := image.NewRGBA(image.Rect(0, 0, 50, 150)) - - src, err := graphicstest.LoadImage("../testdata/gopher.png") - if err != nil { - t.Fatal(err) - } - if err := Thumbnail(dst, src); err != nil { - t.Fatal(err) - } - cmp, err := graphicstest.LoadImage("../testdata/gopher-thumb-50x150.png") - if err != nil { - t.Fatal(err) - } - err = graphicstest.ImageWithinTolerance(dst, cmp, 0) - if err != nil { - t.Error(err) - } -} diff --git a/Godeps/_workspace/src/github.com/disintegration/imaging/LICENSE b/Godeps/_workspace/src/github.com/disintegration/imaging/LICENSE new file mode 100644 index 000000000..95ae410c3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/disintegration/imaging/LICENSE @@ -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.
\ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/disintegration/imaging/README.md b/Godeps/_workspace/src/github.com/disintegration/imaging/README.md new file mode 100644 index 000000000..16ac8cf6c --- /dev/null +++ b/Godeps/_workspace/src/github.com/disintegration/imaging/README.md @@ -0,0 +1,160 @@ +# 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 make a 100x100px thumbnail
+dstImageThumb := imaging.Thumbnail(srcImage, 100, 100, 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)
+
+### 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)
+ }
+}
+```
diff --git a/Godeps/_workspace/src/github.com/disintegration/imaging/adjust.go b/Godeps/_workspace/src/github.com/disintegration/imaging/adjust.go new file mode 100644 index 000000000..9b1b83a4f --- /dev/null +++ b/Godeps/_workspace/src/github.com/disintegration/imaging/adjust.go @@ -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) +} diff --git a/Godeps/_workspace/src/github.com/disintegration/imaging/effects.go b/Godeps/_workspace/src/github.com/disintegration/imaging/effects.go new file mode 100644 index 000000000..fe92e10a2 --- /dev/null +++ b/Godeps/_workspace/src/github.com/disintegration/imaging/effects.go @@ -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 +} diff --git a/Godeps/_workspace/src/github.com/disintegration/imaging/helpers.go b/Godeps/_workspace/src/github.com/disintegration/imaging/helpers.go new file mode 100644 index 000000000..983b64d71 --- /dev/null +++ b/Godeps/_workspace/src/github.com/disintegration/imaging/helpers.go @@ -0,0 +1,392 @@ +/* +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: + dst.Pix[di+0] = uint8(uint16(src.Pix[si+0]) * 0xff / uint16(a)) + dst.Pix[di+1] = uint8(uint16(src.Pix[si+1]) * 0xff / uint16(a)) + dst.Pix[di+2] = uint8(uint16(src.Pix[si+2]) * 0xff / uint16(a)) + } + + 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: + dst.Pix[di+0] = uint8(uint16(src.Pix[si+0]) * 0xff / uint16(a)) + dst.Pix[di+1] = uint8(uint16(src.Pix[si+2]) * 0xff / uint16(a)) + dst.Pix[di+2] = uint8(uint16(src.Pix[si+4]) * 0xff / uint16(a)) + } + + 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) +} diff --git a/Godeps/_workspace/src/github.com/disintegration/imaging/resize.go b/Godeps/_workspace/src/github.com/disintegration/imaging/resize.go new file mode 100644 index 000000000..d2efd5c83 --- /dev/null +++ b/Godeps/_workspace/src/github.com/disintegration/imaging/resize.go @@ -0,0 +1,564 @@ +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) +} + +// 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 { + thumbW, thumbH := width, height + + if thumbW <= 0 || thumbH <= 0 { + return &image.NRGBA{} + } + + srcBounds := img.Bounds() + srcW := srcBounds.Dx() + srcH := srcBounds.Dy() + + if srcW <= 0 || srcH <= 0 { + return &image.NRGBA{} + } + + srcAspectRatio := float64(srcW) / float64(srcH) + thumbAspectRatio := float64(thumbW) / float64(thumbH) + + var tmp image.Image + if srcAspectRatio > thumbAspectRatio { + tmp = Resize(img, 0, thumbH, filter) + } else { + tmp = Resize(img, thumbW, 0, filter) + } + + return CropCenter(tmp, thumbW, thumbH) +} + +// 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 + }, + } +} diff --git a/Godeps/_workspace/src/github.com/disintegration/imaging/tools.go b/Godeps/_workspace/src/github.com/disintegration/imaging/tools.go new file mode 100644 index 000000000..2c39c900a --- /dev/null +++ b/Godeps/_workspace/src/github.com/disintegration/imaging/tools.go @@ -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 +} diff --git a/Godeps/_workspace/src/github.com/disintegration/imaging/transform.go b/Godeps/_workspace/src/github.com/disintegration/imaging/transform.go new file mode 100644 index 000000000..a11601bba --- /dev/null +++ b/Godeps/_workspace/src/github.com/disintegration/imaging/transform.go @@ -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 +} diff --git a/Godeps/_workspace/src/github.com/disintegration/imaging/utils.go b/Godeps/_workspace/src/github.com/disintegration/imaging/utils.go new file mode 100644 index 000000000..8b1ab8adb --- /dev/null +++ b/Godeps/_workspace/src/github.com/disintegration/imaging/utils.go @@ -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) +} diff --git a/Godeps/_workspace/src/github.com/nfnt/resize/.travis.yml b/Godeps/_workspace/src/github.com/nfnt/resize/.travis.yml deleted file mode 100644 index 57bd4a76e..000000000 --- a/Godeps/_workspace/src/github.com/nfnt/resize/.travis.yml +++ /dev/null @@ -1,7 +0,0 @@ -language: go - -go: - - 1.1 - - 1.2 - - 1.3 - - tip diff --git a/Godeps/_workspace/src/github.com/nfnt/resize/LICENSE b/Godeps/_workspace/src/github.com/nfnt/resize/LICENSE deleted file mode 100644 index 7836cad5f..000000000 --- a/Godeps/_workspace/src/github.com/nfnt/resize/LICENSE +++ /dev/null @@ -1,13 +0,0 @@ -Copyright (c) 2012, Jan Schlicht <jan.schlicht@gmail.com> - -Permission to use, copy, modify, and/or distribute this software for any purpose -with or without fee is hereby granted, provided that the above copyright notice -and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS -OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF -THIS SOFTWARE. diff --git a/Godeps/_workspace/src/github.com/nfnt/resize/README.md b/Godeps/_workspace/src/github.com/nfnt/resize/README.md deleted file mode 100644 index 2aefa75c9..000000000 --- a/Godeps/_workspace/src/github.com/nfnt/resize/README.md +++ /dev/null @@ -1,149 +0,0 @@ -Resize -====== - -Image resizing for the [Go programming language](http://golang.org) with common interpolation methods. - -[![Build Status](https://travis-ci.org/nfnt/resize.svg)](https://travis-ci.org/nfnt/resize) - -Installation ------------- - -```bash -$ go get github.com/nfnt/resize -``` - -It's that easy! - -Usage ------ - -This package needs at least Go 1.1. Import package with - -```go -import "github.com/nfnt/resize" -``` - -The resize package provides 2 functions: - -* `resize.Resize` creates a scaled image with new dimensions (`width`, `height`) using the interpolation function `interp`. - If either `width` or `height` is set to 0, it will be set to an aspect ratio preserving value. -* `resize.Thumbnail` downscales an image preserving its aspect ratio to the maximum dimensions (`maxWidth`, `maxHeight`). - It will return the original image if original sizes are smaller than the provided dimensions. - -```go -resize.Resize(width, height uint, img image.Image, interp resize.InterpolationFunction) image.Image -resize.Thumbnail(maxWidth, maxHeight uint, img image.Image, interp resize.InterpolationFunction) image.Image -``` - -The provided interpolation functions are (from fast to slow execution time) - -- `NearestNeighbor`: [Nearest-neighbor interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation) -- `Bilinear`: [Bilinear interpolation](http://en.wikipedia.org/wiki/Bilinear_interpolation) -- `Bicubic`: [Bicubic interpolation](http://en.wikipedia.org/wiki/Bicubic_interpolation) -- `MitchellNetravali`: [Mitchell-Netravali interpolation](http://dl.acm.org/citation.cfm?id=378514) -- `Lanczos2`: [Lanczos resampling](http://en.wikipedia.org/wiki/Lanczos_resampling) with a=2 -- `Lanczos3`: [Lanczos resampling](http://en.wikipedia.org/wiki/Lanczos_resampling) with a=3 - -Which of these methods gives the best results depends on your use case. - -Sample usage: - -```go -package main - -import ( - "github.com/nfnt/resize" - "image/jpeg" - "log" - "os" -) - -func main() { - // open "test.jpg" - file, err := os.Open("test.jpg") - if err != nil { - log.Fatal(err) - } - - // decode jpeg into image.Image - img, err := jpeg.Decode(file) - if err != nil { - log.Fatal(err) - } - file.Close() - - // resize to width 1000 using Lanczos resampling - // and preserve aspect ratio - m := resize.Resize(1000, 0, img, resize.Lanczos3) - - out, err := os.Create("test_resized.jpg") - if err != nil { - log.Fatal(err) - } - defer out.Close() - - // write new image to file - jpeg.Encode(out, m, nil) -} -``` - -Caveats -------- - -* Optimized access routines are used for `image.RGBA`, `image.NRGBA`, `image.RGBA64`, `image.NRGBA64`, `image.YCbCr`, `image.Gray`, and `image.Gray16` types. All other image types are accessed in a generic way that will result in slow processing speed. -* JPEG images are stored in `image.YCbCr`. This image format stores data in a way that will decrease processing speed. A resize may be up to 2 times slower than with `image.RGBA`. - - -Downsizing Samples -------- - -Downsizing is not as simple as it might look like. Images have to be filtered before they are scaled down, otherwise aliasing might occur. -Filtering is highly subjective: Applying too much will blur the whole image, too little will make aliasing become apparent. -Resize tries to provide sane defaults that should suffice in most cases. - -### Artificial sample - -Original image -![Rings](http://nfnt.github.com/img/rings_lg_orig.png) - -<table> -<tr> -<th><img src="http://nfnt.github.com/img/rings_300_NearestNeighbor.png" /><br>Nearest-Neighbor</th> -<th><img src="http://nfnt.github.com/img/rings_300_Bilinear.png" /><br>Bilinear</th> -</tr> -<tr> -<th><img src="http://nfnt.github.com/img/rings_300_Bicubic.png" /><br>Bicubic</th> -<th><img src="http://nfnt.github.com/img/rings_300_MitchellNetravali.png" /><br>Mitchell-Netravali</th> -</tr> -<tr> -<th><img src="http://nfnt.github.com/img/rings_300_Lanczos2.png" /><br>Lanczos2</th> -<th><img src="http://nfnt.github.com/img/rings_300_Lanczos3.png" /><br>Lanczos3</th> -</tr> -</table> - -### Real-Life sample - -Original image -![Original](http://nfnt.github.com/img/IMG_3694_720.jpg) - -<table> -<tr> -<th><img src="http://nfnt.github.com/img/IMG_3694_300_NearestNeighbor.png" /><br>Nearest-Neighbor</th> -<th><img src="http://nfnt.github.com/img/IMG_3694_300_Bilinear.png" /><br>Bilinear</th> -</tr> -<tr> -<th><img src="http://nfnt.github.com/img/IMG_3694_300_Bicubic.png" /><br>Bicubic</th> -<th><img src="http://nfnt.github.com/img/IMG_3694_300_MitchellNetravali.png" /><br>Mitchell-Netravali</th> -</tr> -<tr> -<th><img src="http://nfnt.github.com/img/IMG_3694_300_Lanczos2.png" /><br>Lanczos2</th> -<th><img src="http://nfnt.github.com/img/IMG_3694_300_Lanczos3.png" /><br>Lanczos3</th> -</tr> -</table> - - -License -------- - -Copyright (c) 2012 Jan Schlicht <janschlicht@gmail.com> -Resize is released under a MIT style license. diff --git a/Godeps/_workspace/src/github.com/nfnt/resize/converter.go b/Godeps/_workspace/src/github.com/nfnt/resize/converter.go deleted file mode 100644 index b3dd06b8d..000000000 --- a/Godeps/_workspace/src/github.com/nfnt/resize/converter.go +++ /dev/null @@ -1,452 +0,0 @@ -/* -Copyright (c) 2012, Jan Schlicht <jan.schlicht@gmail.com> - -Permission to use, copy, modify, and/or distribute this software for any purpose -with or without fee is hereby granted, provided that the above copyright notice -and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS -OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF -THIS SOFTWARE. -*/ - -package resize - -import "image" - -// Keep value in [0,255] range. -func clampUint8(in int32) uint8 { - // casting a negative int to an uint will result in an overflown - // large uint. this behavior will be exploited here and in other functions - // to achieve a higher performance. - if uint32(in) < 256 { - return uint8(in) - } - if in > 255 { - return 255 - } - return 0 -} - -// Keep value in [0,65535] range. -func clampUint16(in int64) uint16 { - if uint64(in) < 65536 { - return uint16(in) - } - if in > 65535 { - return 65535 - } - return 0 -} - -func resizeGeneric(in image.Image, out *image.NRGBA64, scale float64, coeffs []int32, offset []int, filterLength int) { - newBounds := out.Bounds() - maxX := in.Bounds().Dx() - 1 - - for x := newBounds.Min.X; x < newBounds.Max.X; x++ { - for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { - var rgba [4]int64 - var sum int64 - start := offset[y] - ci := y * filterLength - for i := 0; i < filterLength; i++ { - coeff := coeffs[ci+i] - if coeff != 0 { - xi := start + i - switch { - case xi < 0: - xi = 0 - case xi >= maxX: - xi = maxX - } - r, g, b, a := in.At(xi+in.Bounds().Min.X, x+in.Bounds().Min.Y).RGBA() - - // reverse alpha-premultiplication. - if a != 0 { - r *= 0xffff - r /= a - g *= 0xffff - g /= a - b *= 0xffff - b /= a - } - - rgba[0] += int64(coeff) * int64(r) - rgba[1] += int64(coeff) * int64(g) - rgba[2] += int64(coeff) * int64(b) - rgba[3] += int64(coeff) * int64(a) - sum += int64(coeff) - } - } - - offset := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*8 - value := clampUint16(rgba[0] / sum) - out.Pix[offset+0] = uint8(value >> 8) - out.Pix[offset+1] = uint8(value) - value = clampUint16(rgba[1] / sum) - out.Pix[offset+2] = uint8(value >> 8) - out.Pix[offset+3] = uint8(value) - value = clampUint16(rgba[2] / sum) - out.Pix[offset+4] = uint8(value >> 8) - out.Pix[offset+5] = uint8(value) - value = clampUint16(rgba[3] / sum) - out.Pix[offset+6] = uint8(value >> 8) - out.Pix[offset+7] = uint8(value) - } - } -} - -func resizeRGBA(in *image.RGBA, out *image.NRGBA, scale float64, coeffs []int16, offset []int, filterLength int) { - newBounds := out.Bounds() - maxX := in.Bounds().Dx() - 1 - - for x := newBounds.Min.X; x < newBounds.Max.X; x++ { - row := in.Pix[x*in.Stride:] - for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { - var rgba [4]int32 - var sum int32 - start := offset[y] - ci := y * filterLength - for i := 0; i < filterLength; i++ { - coeff := coeffs[ci+i] - if coeff != 0 { - xi := start + i - switch { - case uint(xi) < uint(maxX): - xi *= 4 - case xi >= maxX: - xi = 4 * maxX - default: - xi = 0 - } - - r := uint32(row[xi+0]) - g := uint32(row[xi+1]) - b := uint32(row[xi+2]) - a := uint32(row[xi+3]) - - // reverse alpha-premultiplication. - if a != 0 { - r *= 0xff - r /= a - g *= 0xff - g /= a - b *= 0xff - b /= a - } - - rgba[0] += int32(coeff) * int32(r) - rgba[1] += int32(coeff) * int32(g) - rgba[2] += int32(coeff) * int32(b) - rgba[3] += int32(coeff) * int32(a) - sum += int32(coeff) - } - } - - xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*4 - out.Pix[xo+0] = clampUint8(rgba[0] / sum) - out.Pix[xo+1] = clampUint8(rgba[1] / sum) - out.Pix[xo+2] = clampUint8(rgba[2] / sum) - out.Pix[xo+3] = clampUint8(rgba[3] / sum) - } - } -} - -func resizeNRGBA(in *image.NRGBA, out *image.NRGBA, scale float64, coeffs []int16, offset []int, filterLength int) { - newBounds := out.Bounds() - maxX := in.Bounds().Dx() - 1 - - for x := newBounds.Min.X; x < newBounds.Max.X; x++ { - row := in.Pix[x*in.Stride:] - for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { - var rgba [4]int32 - var sum int32 - start := offset[y] - ci := y * filterLength - for i := 0; i < filterLength; i++ { - coeff := coeffs[ci+i] - if coeff != 0 { - xi := start + i - switch { - case uint(xi) < uint(maxX): - xi *= 4 - case xi >= maxX: - xi = 4 * maxX - default: - xi = 0 - } - rgba[0] += int32(coeff) * int32(row[xi+0]) - rgba[1] += int32(coeff) * int32(row[xi+1]) - rgba[2] += int32(coeff) * int32(row[xi+2]) - rgba[3] += int32(coeff) * int32(row[xi+3]) - sum += int32(coeff) - } - } - - xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*4 - out.Pix[xo+0] = clampUint8(rgba[0] / sum) - out.Pix[xo+1] = clampUint8(rgba[1] / sum) - out.Pix[xo+2] = clampUint8(rgba[2] / sum) - out.Pix[xo+3] = clampUint8(rgba[3] / sum) - } - } -} - -func resizeRGBA64(in *image.RGBA64, out *image.NRGBA64, scale float64, coeffs []int32, offset []int, filterLength int) { - newBounds := out.Bounds() - maxX := in.Bounds().Dx() - 1 - - for x := newBounds.Min.X; x < newBounds.Max.X; x++ { - row := in.Pix[x*in.Stride:] - for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { - var rgba [4]int64 - var sum int64 - start := offset[y] - ci := y * filterLength - for i := 0; i < filterLength; i++ { - coeff := coeffs[ci+i] - if coeff != 0 { - xi := start + i - switch { - case uint(xi) < uint(maxX): - xi *= 8 - case xi >= maxX: - xi = 8 * maxX - default: - xi = 0 - } - - r := uint32(uint16(row[xi+0])<<8 | uint16(row[xi+1])) - g := uint32(uint16(row[xi+2])<<8 | uint16(row[xi+3])) - b := uint32(uint16(row[xi+4])<<8 | uint16(row[xi+5])) - a := uint32(uint16(row[xi+6])<<8 | uint16(row[xi+7])) - - // reverse alpha-premultiplication. - if a != 0 { - r *= 0xffff - r /= a - g *= 0xffff - g /= a - b *= 0xffff - b /= a - } - - rgba[0] += int64(coeff) * int64(r) - rgba[1] += int64(coeff) * int64(g) - rgba[2] += int64(coeff) * int64(b) - rgba[3] += int64(coeff) * int64(a) - sum += int64(coeff) - } - } - - xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*8 - value := clampUint16(rgba[0] / sum) - out.Pix[xo+0] = uint8(value >> 8) - out.Pix[xo+1] = uint8(value) - value = clampUint16(rgba[1] / sum) - out.Pix[xo+2] = uint8(value >> 8) - out.Pix[xo+3] = uint8(value) - value = clampUint16(rgba[2] / sum) - out.Pix[xo+4] = uint8(value >> 8) - out.Pix[xo+5] = uint8(value) - value = clampUint16(rgba[3] / sum) - out.Pix[xo+6] = uint8(value >> 8) - out.Pix[xo+7] = uint8(value) - } - } -} - -func resizeNRGBA64(in *image.NRGBA64, out *image.NRGBA64, scale float64, coeffs []int32, offset []int, filterLength int) { - newBounds := out.Bounds() - maxX := in.Bounds().Dx() - 1 - - for x := newBounds.Min.X; x < newBounds.Max.X; x++ { - row := in.Pix[x*in.Stride:] - for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { - var rgba [4]int64 - var sum int64 - start := offset[y] - ci := y * filterLength - for i := 0; i < filterLength; i++ { - coeff := coeffs[ci+i] - if coeff != 0 { - xi := start + i - switch { - case uint(xi) < uint(maxX): - xi *= 8 - case xi >= maxX: - xi = 8 * maxX - default: - xi = 0 - } - rgba[0] += int64(coeff) * int64(uint16(row[xi+0])<<8|uint16(row[xi+1])) - rgba[1] += int64(coeff) * int64(uint16(row[xi+2])<<8|uint16(row[xi+3])) - rgba[2] += int64(coeff) * int64(uint16(row[xi+4])<<8|uint16(row[xi+5])) - rgba[3] += int64(coeff) * int64(uint16(row[xi+6])<<8|uint16(row[xi+7])) - sum += int64(coeff) - } - } - - xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*8 - value := clampUint16(rgba[0] / sum) - out.Pix[xo+0] = uint8(value >> 8) - out.Pix[xo+1] = uint8(value) - value = clampUint16(rgba[1] / sum) - out.Pix[xo+2] = uint8(value >> 8) - out.Pix[xo+3] = uint8(value) - value = clampUint16(rgba[2] / sum) - out.Pix[xo+4] = uint8(value >> 8) - out.Pix[xo+5] = uint8(value) - value = clampUint16(rgba[3] / sum) - out.Pix[xo+6] = uint8(value >> 8) - out.Pix[xo+7] = uint8(value) - } - } -} - -func resizeGray(in *image.Gray, out *image.Gray, scale float64, coeffs []int16, offset []int, filterLength int) { - newBounds := out.Bounds() - maxX := in.Bounds().Dx() - 1 - - for x := newBounds.Min.X; x < newBounds.Max.X; x++ { - row := in.Pix[(x-newBounds.Min.X)*in.Stride:] - for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { - var gray int32 - var sum int32 - start := offset[y] - ci := y * filterLength - for i := 0; i < filterLength; i++ { - coeff := coeffs[ci+i] - if coeff != 0 { - xi := start + i - switch { - case xi < 0: - xi = 0 - case xi >= maxX: - xi = maxX - } - gray += int32(coeff) * int32(row[xi]) - sum += int32(coeff) - } - } - - offset := (y-newBounds.Min.Y)*out.Stride + (x - newBounds.Min.X) - out.Pix[offset] = clampUint8(gray / sum) - } - } -} - -func resizeGray16(in *image.Gray16, out *image.Gray16, scale float64, coeffs []int32, offset []int, filterLength int) { - newBounds := out.Bounds() - maxX := in.Bounds().Dx() - 1 - - for x := newBounds.Min.X; x < newBounds.Max.X; x++ { - row := in.Pix[x*in.Stride:] - for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { - var gray int64 - var sum int64 - start := offset[y] - ci := y * filterLength - for i := 0; i < filterLength; i++ { - coeff := coeffs[ci+i] - if coeff != 0 { - xi := start + i - switch { - case uint(xi) < uint(maxX): - xi *= 2 - case xi >= maxX: - xi = 2 * maxX - default: - xi = 0 - } - gray += int64(coeff) * int64(uint16(row[xi+0])<<8|uint16(row[xi+1])) - sum += int64(coeff) - } - } - - offset := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*2 - value := clampUint16(gray / sum) - out.Pix[offset+0] = uint8(value >> 8) - out.Pix[offset+1] = uint8(value) - } - } -} - -func resizeYCbCr(in *ycc, out *ycc, scale float64, coeffs []int16, offset []int, filterLength int) { - newBounds := out.Bounds() - maxX := in.Bounds().Dx() - 1 - - for x := newBounds.Min.X; x < newBounds.Max.X; x++ { - row := in.Pix[x*in.Stride:] - for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { - var p [3]int32 - var sum int32 - start := offset[y] - ci := y * filterLength - for i := 0; i < filterLength; i++ { - coeff := coeffs[ci+i] - if coeff != 0 { - xi := start + i - switch { - case uint(xi) < uint(maxX): - xi *= 3 - case xi >= maxX: - xi = 3 * maxX - default: - xi = 0 - } - p[0] += int32(coeff) * int32(row[xi+0]) - p[1] += int32(coeff) * int32(row[xi+1]) - p[2] += int32(coeff) * int32(row[xi+2]) - sum += int32(coeff) - } - } - - xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*3 - out.Pix[xo+0] = clampUint8(p[0] / sum) - out.Pix[xo+1] = clampUint8(p[1] / sum) - out.Pix[xo+2] = clampUint8(p[2] / sum) - } - } -} - -func nearestYCbCr(in *ycc, out *ycc, scale float64, coeffs []bool, offset []int, filterLength int) { - newBounds := out.Bounds() - maxX := in.Bounds().Dx() - 1 - - for x := newBounds.Min.X; x < newBounds.Max.X; x++ { - row := in.Pix[x*in.Stride:] - for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { - var p [3]float32 - var sum float32 - start := offset[y] - ci := y * filterLength - for i := 0; i < filterLength; i++ { - if coeffs[ci+i] { - xi := start + i - switch { - case uint(xi) < uint(maxX): - xi *= 3 - case xi >= maxX: - xi = 3 * maxX - default: - xi = 0 - } - p[0] += float32(row[xi+0]) - p[1] += float32(row[xi+1]) - p[2] += float32(row[xi+2]) - sum++ - } - } - - xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*3 - out.Pix[xo+0] = floatToUint8(p[0] / sum) - out.Pix[xo+1] = floatToUint8(p[1] / sum) - out.Pix[xo+2] = floatToUint8(p[2] / sum) - } - } -} diff --git a/Godeps/_workspace/src/github.com/nfnt/resize/converter_test.go b/Godeps/_workspace/src/github.com/nfnt/resize/converter_test.go deleted file mode 100644 index 85639efc2..000000000 --- a/Godeps/_workspace/src/github.com/nfnt/resize/converter_test.go +++ /dev/null @@ -1,43 +0,0 @@ -package resize - -import ( - "testing" -) - -func Test_ClampUint8(t *testing.T) { - var testData = []struct { - in int32 - expected uint8 - }{ - {0, 0}, - {255, 255}, - {128, 128}, - {-2, 0}, - {256, 255}, - } - for _, test := range testData { - actual := clampUint8(test.in) - if actual != test.expected { - t.Fail() - } - } -} - -func Test_ClampUint16(t *testing.T) { - var testData = []struct { - in int64 - expected uint16 - }{ - {0, 0}, - {65535, 65535}, - {128, 128}, - {-2, 0}, - {65536, 65535}, - } - for _, test := range testData { - actual := clampUint16(test.in) - if actual != test.expected { - t.Fail() - } - } -} diff --git a/Godeps/_workspace/src/github.com/nfnt/resize/filters.go b/Godeps/_workspace/src/github.com/nfnt/resize/filters.go deleted file mode 100644 index 4ce04e389..000000000 --- a/Godeps/_workspace/src/github.com/nfnt/resize/filters.go +++ /dev/null @@ -1,143 +0,0 @@ -/* -Copyright (c) 2012, Jan Schlicht <jan.schlicht@gmail.com> - -Permission to use, copy, modify, and/or distribute this software for any purpose -with or without fee is hereby granted, provided that the above copyright notice -and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS -OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF -THIS SOFTWARE. -*/ - -package resize - -import ( - "math" -) - -func nearest(in float64) float64 { - if in >= -0.5 && in < 0.5 { - return 1 - } - return 0 -} - -func linear(in float64) float64 { - in = math.Abs(in) - if in <= 1 { - return 1 - in - } - return 0 -} - -func cubic(in float64) float64 { - in = math.Abs(in) - if in <= 1 { - return in*in*(1.5*in-2.5) + 1.0 - } - if in <= 2 { - return in*(in*(2.5-0.5*in)-4.0) + 2.0 - } - return 0 -} - -func mitchellnetravali(in float64) float64 { - in = math.Abs(in) - if in <= 1 { - return (7.0*in*in*in - 12.0*in*in + 5.33333333333) * 0.16666666666 - } - if in <= 2 { - return (-2.33333333333*in*in*in + 12.0*in*in - 20.0*in + 10.6666666667) * 0.16666666666 - } - return 0 -} - -func sinc(x float64) float64 { - x = math.Abs(x) * math.Pi - if x >= 1.220703e-4 { - return math.Sin(x) / x - } - return 1 -} - -func lanczos2(in float64) float64 { - if in > -2 && in < 2 { - return sinc(in) * sinc(in*0.5) - } - return 0 -} - -func lanczos3(in float64) float64 { - if in > -3 && in < 3 { - return sinc(in) * sinc(in*0.3333333333333333) - } - return 0 -} - -// range [-256,256] -func createWeights8(dy, filterLength int, blur, scale float64, kernel func(float64) float64) ([]int16, []int, int) { - filterLength = filterLength * int(math.Max(math.Ceil(blur*scale), 1)) - filterFactor := math.Min(1./(blur*scale), 1) - - coeffs := make([]int16, dy*filterLength) - start := make([]int, dy) - for y := 0; y < dy; y++ { - interpX := scale*(float64(y)+0.5) - 0.5 - start[y] = int(interpX) - filterLength/2 + 1 - interpX -= float64(start[y]) - for i := 0; i < filterLength; i++ { - in := (interpX - float64(i)) * filterFactor - coeffs[y*filterLength+i] = int16(kernel(in) * 256) - } - } - - return coeffs, start, filterLength -} - -// range [-65536,65536] -func createWeights16(dy, filterLength int, blur, scale float64, kernel func(float64) float64) ([]int32, []int, int) { - filterLength = filterLength * int(math.Max(math.Ceil(blur*scale), 1)) - filterFactor := math.Min(1./(blur*scale), 1) - - coeffs := make([]int32, dy*filterLength) - start := make([]int, dy) - for y := 0; y < dy; y++ { - interpX := scale*(float64(y)+0.5) - 0.5 - start[y] = int(interpX) - filterLength/2 + 1 - interpX -= float64(start[y]) - for i := 0; i < filterLength; i++ { - in := (interpX - float64(i)) * filterFactor - coeffs[y*filterLength+i] = int32(kernel(in) * 65536) - } - } - - return coeffs, start, filterLength -} - -func createWeightsNearest(dy, filterLength int, blur, scale float64) ([]bool, []int, int) { - filterLength = filterLength * int(math.Max(math.Ceil(blur*scale), 1)) - filterFactor := math.Min(1./(blur*scale), 1) - - coeffs := make([]bool, dy*filterLength) - start := make([]int, dy) - for y := 0; y < dy; y++ { - interpX := scale*(float64(y)+0.5) - 0.5 - start[y] = int(interpX) - filterLength/2 + 1 - interpX -= float64(start[y]) - for i := 0; i < filterLength; i++ { - in := (interpX - float64(i)) * filterFactor - if in >= -0.5 && in < 0.5 { - coeffs[y*filterLength+i] = true - } else { - coeffs[y*filterLength+i] = false - } - } - } - - return coeffs, start, filterLength -} diff --git a/Godeps/_workspace/src/github.com/nfnt/resize/nearest.go b/Godeps/_workspace/src/github.com/nfnt/resize/nearest.go deleted file mode 100644 index 888039d85..000000000 --- a/Godeps/_workspace/src/github.com/nfnt/resize/nearest.go +++ /dev/null @@ -1,318 +0,0 @@ -/* -Copyright (c) 2014, Charlie Vieth <charlie.vieth@gmail.com> - -Permission to use, copy, modify, and/or distribute this software for any purpose -with or without fee is hereby granted, provided that the above copyright notice -and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS -OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF -THIS SOFTWARE. -*/ - -package resize - -import "image" - -func floatToUint8(x float32) uint8 { - // Nearest-neighbor values are always - // positive no need to check lower-bound. - if x > 0xfe { - return 0xff - } - return uint8(x) -} - -func floatToUint16(x float32) uint16 { - if x > 0xfffe { - return 0xffff - } - return uint16(x) -} - -func nearestGeneric(in image.Image, out *image.RGBA64, scale float64, coeffs []bool, offset []int, filterLength int) { - newBounds := out.Bounds() - maxX := in.Bounds().Dx() - 1 - - for x := newBounds.Min.X; x < newBounds.Max.X; x++ { - for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { - var rgba [4]float32 - var sum float32 - start := offset[y] - ci := y * filterLength - for i := 0; i < filterLength; i++ { - if coeffs[ci+i] { - xi := start + i - switch { - case xi < 0: - xi = 0 - case xi >= maxX: - xi = maxX - } - r, g, b, a := in.At(xi+in.Bounds().Min.X, x+in.Bounds().Min.Y).RGBA() - rgba[0] += float32(r) - rgba[1] += float32(g) - rgba[2] += float32(b) - rgba[3] += float32(a) - sum++ - } - } - - offset := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*8 - value := floatToUint16(rgba[0] / sum) - out.Pix[offset+0] = uint8(value >> 8) - out.Pix[offset+1] = uint8(value) - value = floatToUint16(rgba[1] / sum) - out.Pix[offset+2] = uint8(value >> 8) - out.Pix[offset+3] = uint8(value) - value = floatToUint16(rgba[2] / sum) - out.Pix[offset+4] = uint8(value >> 8) - out.Pix[offset+5] = uint8(value) - value = floatToUint16(rgba[3] / sum) - out.Pix[offset+6] = uint8(value >> 8) - out.Pix[offset+7] = uint8(value) - } - } -} - -func nearestRGBA(in *image.RGBA, out *image.RGBA, scale float64, coeffs []bool, offset []int, filterLength int) { - newBounds := out.Bounds() - maxX := in.Bounds().Dx() - 1 - - for x := newBounds.Min.X; x < newBounds.Max.X; x++ { - row := in.Pix[x*in.Stride:] - for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { - var rgba [4]float32 - var sum float32 - start := offset[y] - ci := y * filterLength - for i := 0; i < filterLength; i++ { - if coeffs[ci+i] { - xi := start + i - switch { - case uint(xi) < uint(maxX): - xi *= 4 - case xi >= maxX: - xi = 4 * maxX - default: - xi = 0 - } - rgba[0] += float32(row[xi+0]) - rgba[1] += float32(row[xi+1]) - rgba[2] += float32(row[xi+2]) - rgba[3] += float32(row[xi+3]) - sum++ - } - } - - xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*4 - out.Pix[xo+0] = floatToUint8(rgba[0] / sum) - out.Pix[xo+1] = floatToUint8(rgba[1] / sum) - out.Pix[xo+2] = floatToUint8(rgba[2] / sum) - out.Pix[xo+3] = floatToUint8(rgba[3] / sum) - } - } -} - -func nearestNRGBA(in *image.NRGBA, out *image.NRGBA, scale float64, coeffs []bool, offset []int, filterLength int) { - newBounds := out.Bounds() - maxX := in.Bounds().Dx() - 1 - - for x := newBounds.Min.X; x < newBounds.Max.X; x++ { - row := in.Pix[x*in.Stride:] - for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { - var rgba [4]float32 - var sum float32 - start := offset[y] - ci := y * filterLength - for i := 0; i < filterLength; i++ { - if coeffs[ci+i] { - xi := start + i - switch { - case uint(xi) < uint(maxX): - xi *= 4 - case xi >= maxX: - xi = 4 * maxX - default: - xi = 0 - } - rgba[0] += float32(row[xi+0]) - rgba[1] += float32(row[xi+1]) - rgba[2] += float32(row[xi+2]) - rgba[3] += float32(row[xi+3]) - sum++ - } - } - - xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*4 - out.Pix[xo+0] = floatToUint8(rgba[0] / sum) - out.Pix[xo+1] = floatToUint8(rgba[1] / sum) - out.Pix[xo+2] = floatToUint8(rgba[2] / sum) - out.Pix[xo+3] = floatToUint8(rgba[3] / sum) - } - } -} - -func nearestRGBA64(in *image.RGBA64, out *image.RGBA64, scale float64, coeffs []bool, offset []int, filterLength int) { - newBounds := out.Bounds() - maxX := in.Bounds().Dx() - 1 - - for x := newBounds.Min.X; x < newBounds.Max.X; x++ { - row := in.Pix[x*in.Stride:] - for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { - var rgba [4]float32 - var sum float32 - start := offset[y] - ci := y * filterLength - for i := 0; i < filterLength; i++ { - if coeffs[ci+i] { - xi := start + i - switch { - case uint(xi) < uint(maxX): - xi *= 8 - case xi >= maxX: - xi = 8 * maxX - default: - xi = 0 - } - rgba[0] += float32(uint16(row[xi+0])<<8 | uint16(row[xi+1])) - rgba[1] += float32(uint16(row[xi+2])<<8 | uint16(row[xi+3])) - rgba[2] += float32(uint16(row[xi+4])<<8 | uint16(row[xi+5])) - rgba[3] += float32(uint16(row[xi+6])<<8 | uint16(row[xi+7])) - sum++ - } - } - - xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*8 - value := floatToUint16(rgba[0] / sum) - out.Pix[xo+0] = uint8(value >> 8) - out.Pix[xo+1] = uint8(value) - value = floatToUint16(rgba[1] / sum) - out.Pix[xo+2] = uint8(value >> 8) - out.Pix[xo+3] = uint8(value) - value = floatToUint16(rgba[2] / sum) - out.Pix[xo+4] = uint8(value >> 8) - out.Pix[xo+5] = uint8(value) - value = floatToUint16(rgba[3] / sum) - out.Pix[xo+6] = uint8(value >> 8) - out.Pix[xo+7] = uint8(value) - } - } -} - -func nearestNRGBA64(in *image.NRGBA64, out *image.NRGBA64, scale float64, coeffs []bool, offset []int, filterLength int) { - newBounds := out.Bounds() - maxX := in.Bounds().Dx() - 1 - - for x := newBounds.Min.X; x < newBounds.Max.X; x++ { - row := in.Pix[x*in.Stride:] - for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { - var rgba [4]float32 - var sum float32 - start := offset[y] - ci := y * filterLength - for i := 0; i < filterLength; i++ { - if coeffs[ci+i] { - xi := start + i - switch { - case uint(xi) < uint(maxX): - xi *= 8 - case xi >= maxX: - xi = 8 * maxX - default: - xi = 0 - } - rgba[0] += float32(uint16(row[xi+0])<<8 | uint16(row[xi+1])) - rgba[1] += float32(uint16(row[xi+2])<<8 | uint16(row[xi+3])) - rgba[2] += float32(uint16(row[xi+4])<<8 | uint16(row[xi+5])) - rgba[3] += float32(uint16(row[xi+6])<<8 | uint16(row[xi+7])) - sum++ - } - } - - xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*8 - value := floatToUint16(rgba[0] / sum) - out.Pix[xo+0] = uint8(value >> 8) - out.Pix[xo+1] = uint8(value) - value = floatToUint16(rgba[1] / sum) - out.Pix[xo+2] = uint8(value >> 8) - out.Pix[xo+3] = uint8(value) - value = floatToUint16(rgba[2] / sum) - out.Pix[xo+4] = uint8(value >> 8) - out.Pix[xo+5] = uint8(value) - value = floatToUint16(rgba[3] / sum) - out.Pix[xo+6] = uint8(value >> 8) - out.Pix[xo+7] = uint8(value) - } - } -} - -func nearestGray(in *image.Gray, out *image.Gray, scale float64, coeffs []bool, offset []int, filterLength int) { - newBounds := out.Bounds() - maxX := in.Bounds().Dx() - 1 - - for x := newBounds.Min.X; x < newBounds.Max.X; x++ { - row := in.Pix[x*in.Stride:] - for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { - var gray float32 - var sum float32 - start := offset[y] - ci := y * filterLength - for i := 0; i < filterLength; i++ { - if coeffs[ci+i] { - xi := start + i - switch { - case xi < 0: - xi = 0 - case xi >= maxX: - xi = maxX - } - gray += float32(row[xi]) - sum++ - } - } - - offset := (y-newBounds.Min.Y)*out.Stride + (x - newBounds.Min.X) - out.Pix[offset] = floatToUint8(gray / sum) - } - } -} - -func nearestGray16(in *image.Gray16, out *image.Gray16, scale float64, coeffs []bool, offset []int, filterLength int) { - newBounds := out.Bounds() - maxX := in.Bounds().Dx() - 1 - - for x := newBounds.Min.X; x < newBounds.Max.X; x++ { - row := in.Pix[x*in.Stride:] - for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { - var gray float32 - var sum float32 - start := offset[y] - ci := y * filterLength - for i := 0; i < filterLength; i++ { - if coeffs[ci+i] { - xi := start + i - switch { - case uint(xi) < uint(maxX): - xi *= 2 - case xi >= maxX: - xi = 2 * maxX - default: - xi = 0 - } - gray += float32(uint16(row[xi+0])<<8 | uint16(row[xi+1])) - sum++ - } - } - - offset := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*2 - value := floatToUint16(gray / sum) - out.Pix[offset+0] = uint8(value >> 8) - out.Pix[offset+1] = uint8(value) - } - } -} diff --git a/Godeps/_workspace/src/github.com/nfnt/resize/nearest_test.go b/Godeps/_workspace/src/github.com/nfnt/resize/nearest_test.go deleted file mode 100644 index d4a76dda5..000000000 --- a/Godeps/_workspace/src/github.com/nfnt/resize/nearest_test.go +++ /dev/null @@ -1,57 +0,0 @@ -/* -Copyright (c) 2014, Charlie Vieth <charlie.vieth@gmail.com> - -Permission to use, copy, modify, and/or distribute this software for any purpose -with or without fee is hereby granted, provided that the above copyright notice -and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS -OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF -THIS SOFTWARE. -*/ - -package resize - -import "testing" - -func Test_FloatToUint8(t *testing.T) { - var testData = []struct { - in float32 - expected uint8 - }{ - {0, 0}, - {255, 255}, - {128, 128}, - {1, 1}, - {256, 255}, - } - for _, test := range testData { - actual := floatToUint8(test.in) - if actual != test.expected { - t.Fail() - } - } -} - -func Test_FloatToUint16(t *testing.T) { - var testData = []struct { - in float32 - expected uint16 - }{ - {0, 0}, - {65535, 65535}, - {128, 128}, - {1, 1}, - {65536, 65535}, - } - for _, test := range testData { - actual := floatToUint16(test.in) - if actual != test.expected { - t.Fail() - } - } -} diff --git a/Godeps/_workspace/src/github.com/nfnt/resize/resize.go b/Godeps/_workspace/src/github.com/nfnt/resize/resize.go deleted file mode 100644 index c1672432e..000000000 --- a/Godeps/_workspace/src/github.com/nfnt/resize/resize.go +++ /dev/null @@ -1,614 +0,0 @@ -/* -Copyright (c) 2012, Jan Schlicht <jan.schlicht@gmail.com> - -Permission to use, copy, modify, and/or distribute this software for any purpose -with or without fee is hereby granted, provided that the above copyright notice -and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS -OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF -THIS SOFTWARE. -*/ - -// Package resize implements various image resizing methods. -// -// The package works with the Image interface described in the image package. -// Various interpolation methods are provided and multiple processors may be -// utilized in the computations. -// -// Example: -// imgResized := resize.Resize(1000, 0, imgOld, resize.MitchellNetravali) -package resize - -import ( - "image" - "runtime" - "sync" -) - -// An InterpolationFunction provides the parameters that describe an -// interpolation kernel. It returns the number of samples to take -// and the kernel function to use for sampling. -type InterpolationFunction int - -// InterpolationFunction constants -const ( - // Nearest-neighbor interpolation - NearestNeighbor InterpolationFunction = iota - // Bilinear interpolation - Bilinear - // Bicubic interpolation (with cubic hermite spline) - Bicubic - // Mitchell-Netravali interpolation - MitchellNetravali - // Lanczos interpolation (a=2) - Lanczos2 - // Lanczos interpolation (a=3) - Lanczos3 -) - -// kernal, returns an InterpolationFunctions taps and kernel. -func (i InterpolationFunction) kernel() (int, func(float64) float64) { - switch i { - case Bilinear: - return 2, linear - case Bicubic: - return 4, cubic - case MitchellNetravali: - return 4, mitchellnetravali - case Lanczos2: - return 4, lanczos2 - case Lanczos3: - return 6, lanczos3 - default: - // Default to NearestNeighbor. - return 2, nearest - } -} - -// values <1 will sharpen the image -var blur = 1.0 - -// Resize scales an image to new width and height using the interpolation function interp. -// A new image with the given dimensions will be returned. -// If one of the parameters width or height is set to 0, its size will be calculated so that -// the aspect ratio is that of the originating image. -// The resizing algorithm uses channels for parallel computation. -func Resize(width, height uint, img image.Image, interp InterpolationFunction) image.Image { - scaleX, scaleY := calcFactors(width, height, float64(img.Bounds().Dx()), float64(img.Bounds().Dy())) - if width == 0 { - width = uint(0.7 + float64(img.Bounds().Dx())/scaleX) - } - if height == 0 { - height = uint(0.7 + float64(img.Bounds().Dy())/scaleY) - } - - // Trivial case: return input image - if int(width) == img.Bounds().Dx() && int(height) == img.Bounds().Dy() { - return img - } - - if interp == NearestNeighbor { - return resizeNearest(width, height, scaleX, scaleY, img, interp) - } - - taps, kernel := interp.kernel() - cpus := runtime.GOMAXPROCS(0) - wg := sync.WaitGroup{} - - // Generic access to image.Image is slow in tight loops. - // The optimal access has to be determined from the concrete image type. - switch input := img.(type) { - case *image.RGBA: - // 8-bit precision - temp := image.NewNRGBA(image.Rect(0, 0, input.Bounds().Dy(), int(width))) - result := image.NewNRGBA(image.Rect(0, 0, int(width), int(height))) - - // horizontal filter, results in transposed temporary image - coeffs, offset, filterLength := createWeights8(temp.Bounds().Dy(), taps, blur, scaleX, kernel) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(temp, i, cpus).(*image.NRGBA) - go func() { - defer wg.Done() - resizeRGBA(input, slice, scaleX, coeffs, offset, filterLength) - }() - } - wg.Wait() - - // horizontal filter on transposed image, result is not transposed - coeffs, offset, filterLength = createWeights8(result.Bounds().Dy(), taps, blur, scaleY, kernel) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(result, i, cpus).(*image.NRGBA) - go func() { - defer wg.Done() - resizeNRGBA(temp, slice, scaleY, coeffs, offset, filterLength) - }() - } - wg.Wait() - return result - case *image.NRGBA: - // 8-bit precision - temp := image.NewNRGBA(image.Rect(0, 0, input.Bounds().Dy(), int(width))) - result := image.NewNRGBA(image.Rect(0, 0, int(width), int(height))) - - // horizontal filter, results in transposed temporary image - coeffs, offset, filterLength := createWeights8(temp.Bounds().Dy(), taps, blur, scaleX, kernel) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(temp, i, cpus).(*image.NRGBA) - go func() { - defer wg.Done() - resizeNRGBA(input, slice, scaleX, coeffs, offset, filterLength) - }() - } - wg.Wait() - - // horizontal filter on transposed image, result is not transposed - coeffs, offset, filterLength = createWeights8(result.Bounds().Dy(), taps, blur, scaleY, kernel) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(result, i, cpus).(*image.NRGBA) - go func() { - defer wg.Done() - resizeNRGBA(temp, slice, scaleY, coeffs, offset, filterLength) - }() - } - wg.Wait() - return result - - case *image.YCbCr: - // 8-bit precision - // accessing the YCbCr arrays in a tight loop is slow. - // converting the image to ycc increases performance by 2x. - temp := newYCC(image.Rect(0, 0, input.Bounds().Dy(), int(width)), input.SubsampleRatio) - result := newYCC(image.Rect(0, 0, int(width), int(height)), image.YCbCrSubsampleRatio444) - - coeffs, offset, filterLength := createWeights8(temp.Bounds().Dy(), taps, blur, scaleX, kernel) - in := imageYCbCrToYCC(input) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(temp, i, cpus).(*ycc) - go func() { - defer wg.Done() - resizeYCbCr(in, slice, scaleX, coeffs, offset, filterLength) - }() - } - wg.Wait() - - coeffs, offset, filterLength = createWeights8(result.Bounds().Dy(), taps, blur, scaleY, kernel) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(result, i, cpus).(*ycc) - go func() { - defer wg.Done() - resizeYCbCr(temp, slice, scaleY, coeffs, offset, filterLength) - }() - } - wg.Wait() - return result.YCbCr() - case *image.RGBA64: - // 16-bit precision - temp := image.NewNRGBA64(image.Rect(0, 0, input.Bounds().Dy(), int(width))) - result := image.NewNRGBA64(image.Rect(0, 0, int(width), int(height))) - - // horizontal filter, results in transposed temporary image - coeffs, offset, filterLength := createWeights16(temp.Bounds().Dy(), taps, blur, scaleX, kernel) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(temp, i, cpus).(*image.NRGBA64) - go func() { - defer wg.Done() - resizeRGBA64(input, slice, scaleX, coeffs, offset, filterLength) - }() - } - wg.Wait() - - // horizontal filter on transposed image, result is not transposed - coeffs, offset, filterLength = createWeights16(result.Bounds().Dy(), taps, blur, scaleY, kernel) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(result, i, cpus).(*image.NRGBA64) - go func() { - defer wg.Done() - resizeNRGBA64(temp, slice, scaleY, coeffs, offset, filterLength) - }() - } - wg.Wait() - return result - case *image.NRGBA64: - // 16-bit precision - temp := image.NewNRGBA64(image.Rect(0, 0, input.Bounds().Dy(), int(width))) - result := image.NewNRGBA64(image.Rect(0, 0, int(width), int(height))) - - // horizontal filter, results in transposed temporary image - coeffs, offset, filterLength := createWeights16(temp.Bounds().Dy(), taps, blur, scaleX, kernel) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(temp, i, cpus).(*image.NRGBA64) - go func() { - defer wg.Done() - resizeNRGBA64(input, slice, scaleX, coeffs, offset, filterLength) - }() - } - wg.Wait() - - // horizontal filter on transposed image, result is not transposed - coeffs, offset, filterLength = createWeights16(result.Bounds().Dy(), taps, blur, scaleY, kernel) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(result, i, cpus).(*image.NRGBA64) - go func() { - defer wg.Done() - resizeNRGBA64(temp, slice, scaleY, coeffs, offset, filterLength) - }() - } - wg.Wait() - return result - case *image.Gray: - // 8-bit precision - temp := image.NewGray(image.Rect(0, 0, input.Bounds().Dy(), int(width))) - result := image.NewGray(image.Rect(0, 0, int(width), int(height))) - - // horizontal filter, results in transposed temporary image - coeffs, offset, filterLength := createWeights8(temp.Bounds().Dy(), taps, blur, scaleX, kernel) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(temp, i, cpus).(*image.Gray) - go func() { - defer wg.Done() - resizeGray(input, slice, scaleX, coeffs, offset, filterLength) - }() - } - wg.Wait() - - // horizontal filter on transposed image, result is not transposed - coeffs, offset, filterLength = createWeights8(result.Bounds().Dy(), taps, blur, scaleY, kernel) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(result, i, cpus).(*image.Gray) - go func() { - defer wg.Done() - resizeGray(temp, slice, scaleY, coeffs, offset, filterLength) - }() - } - wg.Wait() - return result - case *image.Gray16: - // 16-bit precision - temp := image.NewGray16(image.Rect(0, 0, input.Bounds().Dy(), int(width))) - result := image.NewGray16(image.Rect(0, 0, int(width), int(height))) - - // horizontal filter, results in transposed temporary image - coeffs, offset, filterLength := createWeights16(temp.Bounds().Dy(), taps, blur, scaleX, kernel) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(temp, i, cpus).(*image.Gray16) - go func() { - defer wg.Done() - resizeGray16(input, slice, scaleX, coeffs, offset, filterLength) - }() - } - wg.Wait() - - // horizontal filter on transposed image, result is not transposed - coeffs, offset, filterLength = createWeights16(result.Bounds().Dy(), taps, blur, scaleY, kernel) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(result, i, cpus).(*image.Gray16) - go func() { - defer wg.Done() - resizeGray16(temp, slice, scaleY, coeffs, offset, filterLength) - }() - } - wg.Wait() - return result - default: - // 16-bit precision - temp := image.NewNRGBA64(image.Rect(0, 0, img.Bounds().Dy(), int(width))) - result := image.NewNRGBA64(image.Rect(0, 0, int(width), int(height))) - - // horizontal filter, results in transposed temporary image - coeffs, offset, filterLength := createWeights16(temp.Bounds().Dy(), taps, blur, scaleX, kernel) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(temp, i, cpus).(*image.NRGBA64) - go func() { - defer wg.Done() - resizeGeneric(img, slice, scaleX, coeffs, offset, filterLength) - }() - } - wg.Wait() - - // horizontal filter on transposed image, result is not transposed - coeffs, offset, filterLength = createWeights16(result.Bounds().Dy(), taps, blur, scaleY, kernel) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(result, i, cpus).(*image.NRGBA64) - go func() { - defer wg.Done() - resizeNRGBA64(temp, slice, scaleY, coeffs, offset, filterLength) - }() - } - wg.Wait() - return result - } -} - -func resizeNearest(width, height uint, scaleX, scaleY float64, img image.Image, interp InterpolationFunction) image.Image { - taps, _ := interp.kernel() - cpus := runtime.GOMAXPROCS(0) - wg := sync.WaitGroup{} - - switch input := img.(type) { - case *image.RGBA: - // 8-bit precision - temp := image.NewRGBA(image.Rect(0, 0, input.Bounds().Dy(), int(width))) - result := image.NewRGBA(image.Rect(0, 0, int(width), int(height))) - - // horizontal filter, results in transposed temporary image - coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(temp, i, cpus).(*image.RGBA) - go func() { - defer wg.Done() - nearestRGBA(input, slice, scaleX, coeffs, offset, filterLength) - }() - } - wg.Wait() - - // horizontal filter on transposed image, result is not transposed - coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(result, i, cpus).(*image.RGBA) - go func() { - defer wg.Done() - nearestRGBA(temp, slice, scaleY, coeffs, offset, filterLength) - }() - } - wg.Wait() - return result - case *image.NRGBA: - // 8-bit precision - temp := image.NewNRGBA(image.Rect(0, 0, input.Bounds().Dy(), int(width))) - result := image.NewNRGBA(image.Rect(0, 0, int(width), int(height))) - - // horizontal filter, results in transposed temporary image - coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(temp, i, cpus).(*image.NRGBA) - go func() { - defer wg.Done() - nearestNRGBA(input, slice, scaleX, coeffs, offset, filterLength) - }() - } - wg.Wait() - - // horizontal filter on transposed image, result is not transposed - coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(result, i, cpus).(*image.NRGBA) - go func() { - defer wg.Done() - nearestNRGBA(temp, slice, scaleY, coeffs, offset, filterLength) - }() - } - wg.Wait() - return result - case *image.YCbCr: - // 8-bit precision - // accessing the YCbCr arrays in a tight loop is slow. - // converting the image to ycc increases performance by 2x. - temp := newYCC(image.Rect(0, 0, input.Bounds().Dy(), int(width)), input.SubsampleRatio) - result := newYCC(image.Rect(0, 0, int(width), int(height)), image.YCbCrSubsampleRatio444) - - coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX) - in := imageYCbCrToYCC(input) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(temp, i, cpus).(*ycc) - go func() { - defer wg.Done() - nearestYCbCr(in, slice, scaleX, coeffs, offset, filterLength) - }() - } - wg.Wait() - - coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(result, i, cpus).(*ycc) - go func() { - defer wg.Done() - nearestYCbCr(temp, slice, scaleY, coeffs, offset, filterLength) - }() - } - wg.Wait() - return result.YCbCr() - case *image.RGBA64: - // 16-bit precision - temp := image.NewRGBA64(image.Rect(0, 0, input.Bounds().Dy(), int(width))) - result := image.NewRGBA64(image.Rect(0, 0, int(width), int(height))) - - // horizontal filter, results in transposed temporary image - coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(temp, i, cpus).(*image.RGBA64) - go func() { - defer wg.Done() - nearestRGBA64(input, slice, scaleX, coeffs, offset, filterLength) - }() - } - wg.Wait() - - // horizontal filter on transposed image, result is not transposed - coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(result, i, cpus).(*image.RGBA64) - go func() { - defer wg.Done() - nearestRGBA64(temp, slice, scaleY, coeffs, offset, filterLength) - }() - } - wg.Wait() - return result - case *image.NRGBA64: - // 16-bit precision - temp := image.NewNRGBA64(image.Rect(0, 0, input.Bounds().Dy(), int(width))) - result := image.NewNRGBA64(image.Rect(0, 0, int(width), int(height))) - - // horizontal filter, results in transposed temporary image - coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(temp, i, cpus).(*image.NRGBA64) - go func() { - defer wg.Done() - nearestNRGBA64(input, slice, scaleX, coeffs, offset, filterLength) - }() - } - wg.Wait() - - // horizontal filter on transposed image, result is not transposed - coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(result, i, cpus).(*image.NRGBA64) - go func() { - defer wg.Done() - nearestNRGBA64(temp, slice, scaleY, coeffs, offset, filterLength) - }() - } - wg.Wait() - return result - case *image.Gray: - // 8-bit precision - temp := image.NewGray(image.Rect(0, 0, input.Bounds().Dy(), int(width))) - result := image.NewGray(image.Rect(0, 0, int(width), int(height))) - - // horizontal filter, results in transposed temporary image - coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(temp, i, cpus).(*image.Gray) - go func() { - defer wg.Done() - nearestGray(input, slice, scaleX, coeffs, offset, filterLength) - }() - } - wg.Wait() - - // horizontal filter on transposed image, result is not transposed - coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(result, i, cpus).(*image.Gray) - go func() { - defer wg.Done() - nearestGray(temp, slice, scaleY, coeffs, offset, filterLength) - }() - } - wg.Wait() - return result - case *image.Gray16: - // 16-bit precision - temp := image.NewGray16(image.Rect(0, 0, input.Bounds().Dy(), int(width))) - result := image.NewGray16(image.Rect(0, 0, int(width), int(height))) - - // horizontal filter, results in transposed temporary image - coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(temp, i, cpus).(*image.Gray16) - go func() { - defer wg.Done() - nearestGray16(input, slice, scaleX, coeffs, offset, filterLength) - }() - } - wg.Wait() - - // horizontal filter on transposed image, result is not transposed - coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(result, i, cpus).(*image.Gray16) - go func() { - defer wg.Done() - nearestGray16(temp, slice, scaleY, coeffs, offset, filterLength) - }() - } - wg.Wait() - return result - default: - // 16-bit precision - temp := image.NewRGBA64(image.Rect(0, 0, img.Bounds().Dy(), int(width))) - result := image.NewRGBA64(image.Rect(0, 0, int(width), int(height))) - - // horizontal filter, results in transposed temporary image - coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(temp, i, cpus).(*image.RGBA64) - go func() { - defer wg.Done() - nearestGeneric(img, slice, scaleX, coeffs, offset, filterLength) - }() - } - wg.Wait() - - // horizontal filter on transposed image, result is not transposed - coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(result, i, cpus).(*image.RGBA64) - go func() { - defer wg.Done() - nearestRGBA64(temp, slice, scaleY, coeffs, offset, filterLength) - }() - } - wg.Wait() - return result - } - -} - -// Calculates scaling factors using old and new image dimensions. -func calcFactors(width, height uint, oldWidth, oldHeight float64) (scaleX, scaleY float64) { - if width == 0 { - if height == 0 { - scaleX = 1.0 - scaleY = 1.0 - } else { - scaleY = oldHeight / float64(height) - scaleX = scaleY - } - } else { - scaleX = oldWidth / float64(width) - if height == 0 { - scaleY = scaleX - } else { - scaleY = oldHeight / float64(height) - } - } - return -} - -type imageWithSubImage interface { - image.Image - SubImage(image.Rectangle) image.Image -} - -func makeSlice(img imageWithSubImage, i, n int) image.Image { - return img.SubImage(image.Rect(img.Bounds().Min.X, img.Bounds().Min.Y+i*img.Bounds().Dy()/n, img.Bounds().Max.X, img.Bounds().Min.Y+(i+1)*img.Bounds().Dy()/n)) -} diff --git a/Godeps/_workspace/src/github.com/nfnt/resize/resize_test.go b/Godeps/_workspace/src/github.com/nfnt/resize/resize_test.go deleted file mode 100644 index 6f6911316..000000000 --- a/Godeps/_workspace/src/github.com/nfnt/resize/resize_test.go +++ /dev/null @@ -1,314 +0,0 @@ -package resize - -import ( - "image" - "image/color" - "runtime" - "testing" -) - -var img = image.NewGray16(image.Rect(0, 0, 3, 3)) - -func init() { - runtime.GOMAXPROCS(runtime.NumCPU()) - img.Set(1, 1, color.White) -} - -func Test_Param1(t *testing.T) { - m := Resize(0, 0, img, NearestNeighbor) - if m.Bounds() != img.Bounds() { - t.Fail() - } -} - -func Test_Param2(t *testing.T) { - m := Resize(100, 0, img, NearestNeighbor) - if m.Bounds() != image.Rect(0, 0, 100, 100) { - t.Fail() - } -} - -func Test_ZeroImg(t *testing.T) { - zeroImg := image.NewGray16(image.Rect(0, 0, 0, 0)) - - m := Resize(0, 0, zeroImg, NearestNeighbor) - if m.Bounds() != zeroImg.Bounds() { - t.Fail() - } -} - -func Test_CorrectResize(t *testing.T) { - zeroImg := image.NewGray16(image.Rect(0, 0, 256, 256)) - - m := Resize(60, 0, zeroImg, NearestNeighbor) - if m.Bounds() != image.Rect(0, 0, 60, 60) { - t.Fail() - } -} - -func Test_SameColorWithRGBA(t *testing.T) { - img := image.NewRGBA(image.Rect(0, 0, 20, 20)) - for y := img.Bounds().Min.Y; y < img.Bounds().Max.Y; y++ { - for x := img.Bounds().Min.X; x < img.Bounds().Max.X; x++ { - img.SetRGBA(x, y, color.RGBA{0x80, 0x80, 0x80, 0xFF}) - } - } - out := Resize(10, 10, img, Lanczos3) - for y := out.Bounds().Min.Y; y < out.Bounds().Max.Y; y++ { - for x := out.Bounds().Min.X; x < out.Bounds().Max.X; x++ { - color := out.At(x, y).(color.NRGBA) - if color.R != 0x80 || color.G != 0x80 || color.B != 0x80 || color.A != 0xFF { - t.Errorf("%+v", color) - } - } - } -} - -func Test_SameColorWithNRGBA(t *testing.T) { - img := image.NewNRGBA(image.Rect(0, 0, 20, 20)) - for y := img.Bounds().Min.Y; y < img.Bounds().Max.Y; y++ { - for x := img.Bounds().Min.X; x < img.Bounds().Max.X; x++ { - img.SetNRGBA(x, y, color.NRGBA{0x80, 0x80, 0x80, 0xFF}) - } - } - out := Resize(10, 10, img, Lanczos3) - for y := out.Bounds().Min.Y; y < out.Bounds().Max.Y; y++ { - for x := out.Bounds().Min.X; x < out.Bounds().Max.X; x++ { - color := out.At(x, y).(color.NRGBA) - if color.R != 0x80 || color.G != 0x80 || color.B != 0x80 || color.A != 0xFF { - t.Errorf("%+v", color) - } - } - } -} - -func Test_SameColorWithRGBA64(t *testing.T) { - img := image.NewRGBA64(image.Rect(0, 0, 20, 20)) - for y := img.Bounds().Min.Y; y < img.Bounds().Max.Y; y++ { - for x := img.Bounds().Min.X; x < img.Bounds().Max.X; x++ { - img.SetRGBA64(x, y, color.RGBA64{0x8000, 0x8000, 0x8000, 0xFFFF}) - } - } - out := Resize(10, 10, img, Lanczos3) - for y := out.Bounds().Min.Y; y < out.Bounds().Max.Y; y++ { - for x := out.Bounds().Min.X; x < out.Bounds().Max.X; x++ { - color := out.At(x, y).(color.NRGBA64) - if color.R != 0x8000 || color.G != 0x8000 || color.B != 0x8000 || color.A != 0xFFFF { - t.Errorf("%+v", color) - } - } - } -} - -func Test_SameColorWithNRGBA64(t *testing.T) { - img := image.NewNRGBA64(image.Rect(0, 0, 20, 20)) - for y := img.Bounds().Min.Y; y < img.Bounds().Max.Y; y++ { - for x := img.Bounds().Min.X; x < img.Bounds().Max.X; x++ { - img.SetNRGBA64(x, y, color.NRGBA64{0x8000, 0x8000, 0x8000, 0xFFFF}) - } - } - out := Resize(10, 10, img, Lanczos3) - for y := out.Bounds().Min.Y; y < out.Bounds().Max.Y; y++ { - for x := out.Bounds().Min.X; x < out.Bounds().Max.X; x++ { - color := out.At(x, y).(color.NRGBA64) - if color.R != 0x8000 || color.G != 0x8000 || color.B != 0x8000 || color.A != 0xFFFF { - t.Errorf("%+v", color) - } - } - } -} - -func Test_SameColorWithGray(t *testing.T) { - img := image.NewGray(image.Rect(0, 0, 20, 20)) - for y := img.Bounds().Min.Y; y < img.Bounds().Max.Y; y++ { - for x := img.Bounds().Min.X; x < img.Bounds().Max.X; x++ { - img.SetGray(x, y, color.Gray{0x80}) - } - } - out := Resize(10, 10, img, Lanczos3) - for y := out.Bounds().Min.Y; y < out.Bounds().Max.Y; y++ { - for x := out.Bounds().Min.X; x < out.Bounds().Max.X; x++ { - color := out.At(x, y).(color.Gray) - if color.Y != 0x80 { - t.Errorf("%+v", color) - } - } - } -} - -func Test_SameColorWithGray16(t *testing.T) { - img := image.NewGray16(image.Rect(0, 0, 20, 20)) - for y := img.Bounds().Min.Y; y < img.Bounds().Max.Y; y++ { - for x := img.Bounds().Min.X; x < img.Bounds().Max.X; x++ { - img.SetGray16(x, y, color.Gray16{0x8000}) - } - } - out := Resize(10, 10, img, Lanczos3) - for y := out.Bounds().Min.Y; y < out.Bounds().Max.Y; y++ { - for x := out.Bounds().Min.X; x < out.Bounds().Max.X; x++ { - color := out.At(x, y).(color.Gray16) - if color.Y != 0x8000 { - t.Errorf("%+v", color) - } - } - } -} - -func Test_Bounds(t *testing.T) { - img := image.NewRGBA(image.Rect(20, 10, 200, 99)) - out := Resize(80, 80, img, Lanczos2) - out.At(0, 0) -} - -func Test_SameSizeReturnsOriginal(t *testing.T) { - img := image.NewRGBA(image.Rect(0, 0, 10, 10)) - out := Resize(0, 0, img, Lanczos2) - - if img != out { - t.Fail() - } - - out = Resize(10, 10, img, Lanczos2) - - if img != out { - t.Fail() - } -} - -func Test_PixelCoordinates(t *testing.T) { - checkers := image.NewGray(image.Rect(0, 0, 4, 4)) - checkers.Pix = []uint8{ - 255, 0, 255, 0, - 0, 255, 0, 255, - 255, 0, 255, 0, - 0, 255, 0, 255, - } - - resized := Resize(12, 12, checkers, NearestNeighbor).(*image.Gray) - - if resized.Pix[0] != 255 || resized.Pix[1] != 255 || resized.Pix[2] != 255 { - t.Fail() - } - - if resized.Pix[3] != 0 || resized.Pix[4] != 0 || resized.Pix[5] != 0 { - t.Fail() - } -} - -func Test_ResizeWithPremultipliedAlpha(t *testing.T) { - img := image.NewRGBA(image.Rect(0, 0, 1, 4)) - for y := img.Bounds().Min.Y; y < img.Bounds().Max.Y; y++ { - // 0x80 = 0.5 * 0xFF. - img.SetRGBA(0, y, color.RGBA{0x80, 0x80, 0x80, 0x80}) - } - - out := Resize(1, 2, img, MitchellNetravali) - - outputColor := out.At(0, 0).(color.NRGBA) - if outputColor.R != 0xFF { - t.Fail() - } -} - -const ( - // Use a small image size for benchmarks. We don't want memory performance - // to affect the benchmark results. - benchMaxX = 250 - benchMaxY = 250 - - // Resize values near the original size require increase the amount of time - // resize spends converting the image. - benchWidth = 200 - benchHeight = 200 -) - -func benchRGBA(b *testing.B, interp InterpolationFunction) { - m := image.NewRGBA(image.Rect(0, 0, benchMaxX, benchMaxY)) - // Initialize m's pixels to create a non-uniform image. - for y := m.Rect.Min.Y; y < m.Rect.Max.Y; y++ { - for x := m.Rect.Min.X; x < m.Rect.Max.X; x++ { - i := m.PixOffset(x, y) - m.Pix[i+0] = uint8(y + 4*x) - m.Pix[i+1] = uint8(y + 4*x) - m.Pix[i+2] = uint8(y + 4*x) - m.Pix[i+3] = uint8(4*y + x) - } - } - - var out image.Image - b.ResetTimer() - for i := 0; i < b.N; i++ { - out = Resize(benchWidth, benchHeight, m, interp) - } - out.At(0, 0) -} - -// The names of some interpolation functions are truncated so that the columns -// of 'go test -bench' line up. -func Benchmark_Nearest_RGBA(b *testing.B) { - benchRGBA(b, NearestNeighbor) -} - -func Benchmark_Bilinear_RGBA(b *testing.B) { - benchRGBA(b, Bilinear) -} - -func Benchmark_Bicubic_RGBA(b *testing.B) { - benchRGBA(b, Bicubic) -} - -func Benchmark_Mitchell_RGBA(b *testing.B) { - benchRGBA(b, MitchellNetravali) -} - -func Benchmark_Lanczos2_RGBA(b *testing.B) { - benchRGBA(b, Lanczos2) -} - -func Benchmark_Lanczos3_RGBA(b *testing.B) { - benchRGBA(b, Lanczos3) -} - -func benchYCbCr(b *testing.B, interp InterpolationFunction) { - m := image.NewYCbCr(image.Rect(0, 0, benchMaxX, benchMaxY), image.YCbCrSubsampleRatio422) - // Initialize m's pixels to create a non-uniform image. - for y := m.Rect.Min.Y; y < m.Rect.Max.Y; y++ { - for x := m.Rect.Min.X; x < m.Rect.Max.X; x++ { - yi := m.YOffset(x, y) - ci := m.COffset(x, y) - m.Y[yi] = uint8(16*y + x) - m.Cb[ci] = uint8(y + 16*x) - m.Cr[ci] = uint8(y + 16*x) - } - } - var out image.Image - b.ResetTimer() - for i := 0; i < b.N; i++ { - out = Resize(benchWidth, benchHeight, m, interp) - } - out.At(0, 0) -} - -func Benchmark_Nearest_YCC(b *testing.B) { - benchYCbCr(b, NearestNeighbor) -} - -func Benchmark_Bilinear_YCC(b *testing.B) { - benchYCbCr(b, Bilinear) -} - -func Benchmark_Bicubic_YCC(b *testing.B) { - benchYCbCr(b, Bicubic) -} - -func Benchmark_Mitchell_YCC(b *testing.B) { - benchYCbCr(b, MitchellNetravali) -} - -func Benchmark_Lanczos2_YCC(b *testing.B) { - benchYCbCr(b, Lanczos2) -} - -func Benchmark_Lanczos3_YCC(b *testing.B) { - benchYCbCr(b, Lanczos3) -} diff --git a/Godeps/_workspace/src/github.com/nfnt/resize/thumbnail.go b/Godeps/_workspace/src/github.com/nfnt/resize/thumbnail.go deleted file mode 100644 index 9efc246be..000000000 --- a/Godeps/_workspace/src/github.com/nfnt/resize/thumbnail.go +++ /dev/null @@ -1,55 +0,0 @@ -/* -Copyright (c) 2012, Jan Schlicht <jan.schlicht@gmail.com> - -Permission to use, copy, modify, and/or distribute this software for any purpose -with or without fee is hereby granted, provided that the above copyright notice -and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS -OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF -THIS SOFTWARE. -*/ - -package resize - -import ( - "image" -) - -// Thumbnail will downscale provided image to max width and height preserving -// original aspect ratio and using the interpolation function interp. -// It will return original image, without processing it, if original sizes -// are already smaller than provided constraints. -func Thumbnail(maxWidth, maxHeight uint, img image.Image, interp InterpolationFunction) image.Image { - origBounds := img.Bounds() - origWidth := uint(origBounds.Dx()) - origHeight := uint(origBounds.Dy()) - newWidth, newHeight := origWidth, origHeight - - // Return original image if it have same or smaller size as constraints - if maxWidth >= origWidth && maxHeight >= origHeight { - return img - } - - // Preserve aspect ratio - if origWidth > maxWidth { - newHeight = uint(origHeight * maxWidth / origWidth) - if newHeight < 1 { - newHeight = 1 - } - newWidth = maxWidth - } - - if newHeight > maxHeight { - newWidth = uint(newWidth * maxHeight / newHeight) - if newWidth < 1 { - newWidth = 1 - } - newHeight = maxHeight - } - return Resize(newWidth, newHeight, img, interp) -} diff --git a/Godeps/_workspace/src/github.com/nfnt/resize/thumbnail_test.go b/Godeps/_workspace/src/github.com/nfnt/resize/thumbnail_test.go deleted file mode 100644 index bd9875b2b..000000000 --- a/Godeps/_workspace/src/github.com/nfnt/resize/thumbnail_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package resize - -import ( - "image" - "runtime" - "testing" -) - -func init() { - runtime.GOMAXPROCS(runtime.NumCPU()) -} - -var thumbnailTests = []struct { - origWidth int - origHeight int - maxWidth uint - maxHeight uint - expectedWidth uint - expectedHeight uint -}{ - {5, 5, 10, 10, 5, 5}, - {10, 10, 5, 5, 5, 5}, - {10, 50, 10, 10, 2, 10}, - {50, 10, 10, 10, 10, 2}, - {50, 100, 60, 90, 45, 90}, - {120, 100, 60, 90, 60, 50}, - {200, 250, 200, 150, 120, 150}, -} - -func TestThumbnail(t *testing.T) { - for i, tt := range thumbnailTests { - img := image.NewGray16(image.Rect(0, 0, tt.origWidth, tt.origHeight)) - - outImg := Thumbnail(tt.maxWidth, tt.maxHeight, img, NearestNeighbor) - - newWidth := uint(outImg.Bounds().Dx()) - newHeight := uint(outImg.Bounds().Dy()) - if newWidth != tt.expectedWidth || - newHeight != tt.expectedHeight { - t.Errorf("%d. Thumbnail(%v, %v, img, NearestNeighbor) => "+ - "width: %v, height: %v, want width: %v, height: %v", - i, tt.maxWidth, tt.maxHeight, - newWidth, newHeight, tt.expectedWidth, tt.expectedHeight, - ) - } - } -} diff --git a/Godeps/_workspace/src/github.com/nfnt/resize/ycc.go b/Godeps/_workspace/src/github.com/nfnt/resize/ycc.go deleted file mode 100644 index 104159955..000000000 --- a/Godeps/_workspace/src/github.com/nfnt/resize/ycc.go +++ /dev/null @@ -1,227 +0,0 @@ -/* -Copyright (c) 2014, Charlie Vieth <charlie.vieth@gmail.com> - -Permission to use, copy, modify, and/or distribute this software for any purpose -with or without fee is hereby granted, provided that the above copyright notice -and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS -OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF -THIS SOFTWARE. -*/ - -package resize - -import ( - "image" - "image/color" -) - -// ycc is an in memory YCbCr image. The Y, Cb and Cr samples are held in a -// single slice to increase resizing performance. -type ycc struct { - // Pix holds the image's pixels, in Y, Cb, Cr order. The pixel at - // (x, y) starts at Pix[(y-Rect.Min.Y)*Stride + (x-Rect.Min.X)*3]. - Pix []uint8 - // Stride is the Pix stride (in bytes) between vertically adjacent pixels. - Stride int - // Rect is the image's bounds. - Rect image.Rectangle - // SubsampleRatio is the subsample ratio of the original YCbCr image. - SubsampleRatio image.YCbCrSubsampleRatio -} - -// PixOffset returns the index of the first element of Pix that corresponds to -// the pixel at (x, y). -func (p *ycc) PixOffset(x, y int) int { - return (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*3 -} - -func (p *ycc) Bounds() image.Rectangle { - return p.Rect -} - -func (p *ycc) ColorModel() color.Model { - return color.YCbCrModel -} - -func (p *ycc) At(x, y int) color.Color { - if !(image.Point{x, y}.In(p.Rect)) { - return color.YCbCr{} - } - i := p.PixOffset(x, y) - return color.YCbCr{ - p.Pix[i+0], - p.Pix[i+1], - p.Pix[i+2], - } -} - -func (p *ycc) Opaque() bool { - return true -} - -// SubImage returns an image representing the portion of the image p visible -// through r. The returned value shares pixels with the original image. -func (p *ycc) SubImage(r image.Rectangle) image.Image { - r = r.Intersect(p.Rect) - if r.Empty() { - return &ycc{SubsampleRatio: p.SubsampleRatio} - } - i := p.PixOffset(r.Min.X, r.Min.Y) - return &ycc{ - Pix: p.Pix[i:], - Stride: p.Stride, - Rect: r, - SubsampleRatio: p.SubsampleRatio, - } -} - -// newYCC returns a new ycc with the given bounds and subsample ratio. -func newYCC(r image.Rectangle, s image.YCbCrSubsampleRatio) *ycc { - w, h := r.Dx(), r.Dy() - buf := make([]uint8, 3*w*h) - return &ycc{Pix: buf, Stride: 3 * w, Rect: r, SubsampleRatio: s} -} - -// YCbCr converts ycc to a YCbCr image with the same subsample ratio -// as the YCbCr image that ycc was generated from. -func (p *ycc) YCbCr() *image.YCbCr { - ycbcr := image.NewYCbCr(p.Rect, p.SubsampleRatio) - var off int - - switch ycbcr.SubsampleRatio { - case image.YCbCrSubsampleRatio422: - for y := ycbcr.Rect.Min.Y; y < ycbcr.Rect.Max.Y; y++ { - yy := (y - ycbcr.Rect.Min.Y) * ycbcr.YStride - cy := (y - ycbcr.Rect.Min.Y) * ycbcr.CStride - for x := ycbcr.Rect.Min.X; x < ycbcr.Rect.Max.X; x++ { - xx := (x - ycbcr.Rect.Min.X) - yi := yy + xx - ci := cy + xx/2 - ycbcr.Y[yi] = p.Pix[off+0] - ycbcr.Cb[ci] = p.Pix[off+1] - ycbcr.Cr[ci] = p.Pix[off+2] - off += 3 - } - } - case image.YCbCrSubsampleRatio420: - for y := ycbcr.Rect.Min.Y; y < ycbcr.Rect.Max.Y; y++ { - yy := (y - ycbcr.Rect.Min.Y) * ycbcr.YStride - cy := (y/2 - ycbcr.Rect.Min.Y/2) * ycbcr.CStride - for x := ycbcr.Rect.Min.X; x < ycbcr.Rect.Max.X; x++ { - xx := (x - ycbcr.Rect.Min.X) - yi := yy + xx - ci := cy + xx/2 - ycbcr.Y[yi] = p.Pix[off+0] - ycbcr.Cb[ci] = p.Pix[off+1] - ycbcr.Cr[ci] = p.Pix[off+2] - off += 3 - } - } - case image.YCbCrSubsampleRatio440: - for y := ycbcr.Rect.Min.Y; y < ycbcr.Rect.Max.Y; y++ { - yy := (y - ycbcr.Rect.Min.Y) * ycbcr.YStride - cy := (y/2 - ycbcr.Rect.Min.Y/2) * ycbcr.CStride - for x := ycbcr.Rect.Min.X; x < ycbcr.Rect.Max.X; x++ { - xx := (x - ycbcr.Rect.Min.X) - yi := yy + xx - ci := cy + xx - ycbcr.Y[yi] = p.Pix[off+0] - ycbcr.Cb[ci] = p.Pix[off+1] - ycbcr.Cr[ci] = p.Pix[off+2] - off += 3 - } - } - default: - // Default to 4:4:4 subsampling. - for y := ycbcr.Rect.Min.Y; y < ycbcr.Rect.Max.Y; y++ { - yy := (y - ycbcr.Rect.Min.Y) * ycbcr.YStride - cy := (y - ycbcr.Rect.Min.Y) * ycbcr.CStride - for x := ycbcr.Rect.Min.X; x < ycbcr.Rect.Max.X; x++ { - xx := (x - ycbcr.Rect.Min.X) - yi := yy + xx - ci := cy + xx - ycbcr.Y[yi] = p.Pix[off+0] - ycbcr.Cb[ci] = p.Pix[off+1] - ycbcr.Cr[ci] = p.Pix[off+2] - off += 3 - } - } - } - return ycbcr -} - -// imageYCbCrToYCC converts a YCbCr image to a ycc image for resizing. -func imageYCbCrToYCC(in *image.YCbCr) *ycc { - w, h := in.Rect.Dx(), in.Rect.Dy() - r := image.Rect(0, 0, w, h) - buf := make([]uint8, 3*w*h) - p := ycc{Pix: buf, Stride: 3 * w, Rect: r, SubsampleRatio: in.SubsampleRatio} - var off int - - switch in.SubsampleRatio { - case image.YCbCrSubsampleRatio422: - for y := in.Rect.Min.Y; y < in.Rect.Max.Y; y++ { - yy := (y - in.Rect.Min.Y) * in.YStride - cy := (y - in.Rect.Min.Y) * in.CStride - for x := in.Rect.Min.X; x < in.Rect.Max.X; x++ { - xx := (x - in.Rect.Min.X) - yi := yy + xx - ci := cy + xx/2 - p.Pix[off+0] = in.Y[yi] - p.Pix[off+1] = in.Cb[ci] - p.Pix[off+2] = in.Cr[ci] - off += 3 - } - } - case image.YCbCrSubsampleRatio420: - for y := in.Rect.Min.Y; y < in.Rect.Max.Y; y++ { - yy := (y - in.Rect.Min.Y) * in.YStride - cy := (y/2 - in.Rect.Min.Y/2) * in.CStride - for x := in.Rect.Min.X; x < in.Rect.Max.X; x++ { - xx := (x - in.Rect.Min.X) - yi := yy + xx - ci := cy + xx/2 - p.Pix[off+0] = in.Y[yi] - p.Pix[off+1] = in.Cb[ci] - p.Pix[off+2] = in.Cr[ci] - off += 3 - } - } - case image.YCbCrSubsampleRatio440: - for y := in.Rect.Min.Y; y < in.Rect.Max.Y; y++ { - yy := (y - in.Rect.Min.Y) * in.YStride - cy := (y/2 - in.Rect.Min.Y/2) * in.CStride - for x := in.Rect.Min.X; x < in.Rect.Max.X; x++ { - xx := (x - in.Rect.Min.X) - yi := yy + xx - ci := cy + xx - p.Pix[off+0] = in.Y[yi] - p.Pix[off+1] = in.Cb[ci] - p.Pix[off+2] = in.Cr[ci] - off += 3 - } - } - default: - // Default to 4:4:4 subsampling. - for y := in.Rect.Min.Y; y < in.Rect.Max.Y; y++ { - yy := (y - in.Rect.Min.Y) * in.YStride - cy := (y - in.Rect.Min.Y) * in.CStride - for x := in.Rect.Min.X; x < in.Rect.Max.X; x++ { - xx := (x - in.Rect.Min.X) - yi := yy + xx - ci := cy + xx - p.Pix[off+0] = in.Y[yi] - p.Pix[off+1] = in.Cb[ci] - p.Pix[off+2] = in.Cr[ci] - off += 3 - } - } - } - return &p -} diff --git a/Godeps/_workspace/src/github.com/nfnt/resize/ycc_test.go b/Godeps/_workspace/src/github.com/nfnt/resize/ycc_test.go deleted file mode 100644 index 54d53d157..000000000 --- a/Godeps/_workspace/src/github.com/nfnt/resize/ycc_test.go +++ /dev/null @@ -1,214 +0,0 @@ -/* -Copyright (c) 2014, Charlie Vieth <charlie.vieth@gmail.com> - -Permission to use, copy, modify, and/or distribute this software for any purpose -with or without fee is hereby granted, provided that the above copyright notice -and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS -OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF -THIS SOFTWARE. -*/ - -package resize - -import ( - "image" - "image/color" - "testing" -) - -type Image interface { - image.Image - SubImage(image.Rectangle) image.Image -} - -func TestImage(t *testing.T) { - testImage := []Image{ - newYCC(image.Rect(0, 0, 10, 10), image.YCbCrSubsampleRatio420), - newYCC(image.Rect(0, 0, 10, 10), image.YCbCrSubsampleRatio422), - newYCC(image.Rect(0, 0, 10, 10), image.YCbCrSubsampleRatio440), - newYCC(image.Rect(0, 0, 10, 10), image.YCbCrSubsampleRatio444), - } - for _, m := range testImage { - if !image.Rect(0, 0, 10, 10).Eq(m.Bounds()) { - t.Errorf("%T: want bounds %v, got %v", - m, image.Rect(0, 0, 10, 10), m.Bounds()) - continue - } - m = m.SubImage(image.Rect(3, 2, 9, 8)).(Image) - if !image.Rect(3, 2, 9, 8).Eq(m.Bounds()) { - t.Errorf("%T: sub-image want bounds %v, got %v", - m, image.Rect(3, 2, 9, 8), m.Bounds()) - continue - } - // Test that taking an empty sub-image starting at a corner does not panic. - m.SubImage(image.Rect(0, 0, 0, 0)) - m.SubImage(image.Rect(10, 0, 10, 0)) - m.SubImage(image.Rect(0, 10, 0, 10)) - m.SubImage(image.Rect(10, 10, 10, 10)) - } -} - -func TestConvertYCbCr(t *testing.T) { - testImage := []Image{ - image.NewYCbCr(image.Rect(0, 0, 50, 50), image.YCbCrSubsampleRatio420), - image.NewYCbCr(image.Rect(0, 0, 50, 50), image.YCbCrSubsampleRatio422), - image.NewYCbCr(image.Rect(0, 0, 50, 50), image.YCbCrSubsampleRatio440), - image.NewYCbCr(image.Rect(0, 0, 50, 50), image.YCbCrSubsampleRatio444), - } - - for _, img := range testImage { - m := img.(*image.YCbCr) - for y := m.Rect.Min.Y; y < m.Rect.Max.Y; y++ { - for x := m.Rect.Min.X; x < m.Rect.Max.X; x++ { - yi := m.YOffset(x, y) - ci := m.COffset(x, y) - m.Y[yi] = uint8(16*y + x) - m.Cb[ci] = uint8(y + 16*x) - m.Cr[ci] = uint8(y + 16*x) - } - } - - // test conversion from YCbCr to ycc - yc := imageYCbCrToYCC(m) - for y := m.Rect.Min.Y; y < m.Rect.Max.Y; y++ { - for x := m.Rect.Min.X; x < m.Rect.Max.X; x++ { - ystride := 3 * (m.Rect.Max.X - m.Rect.Min.X) - xstride := 3 - yi := m.YOffset(x, y) - ci := m.COffset(x, y) - si := (y * ystride) + (x * xstride) - if m.Y[yi] != yc.Pix[si] { - t.Errorf("Err Y - found: %d expected: %d x: %d y: %d yi: %d si: %d", - m.Y[yi], yc.Pix[si], x, y, yi, si) - } - if m.Cb[ci] != yc.Pix[si+1] { - t.Errorf("Err Cb - found: %d expected: %d x: %d y: %d ci: %d si: %d", - m.Cb[ci], yc.Pix[si+1], x, y, ci, si+1) - } - if m.Cr[ci] != yc.Pix[si+2] { - t.Errorf("Err Cr - found: %d expected: %d x: %d y: %d ci: %d si: %d", - m.Cr[ci], yc.Pix[si+2], x, y, ci, si+2) - } - } - } - - // test conversion from ycc back to YCbCr - ym := yc.YCbCr() - for y := m.Rect.Min.Y; y < m.Rect.Max.Y; y++ { - for x := m.Rect.Min.X; x < m.Rect.Max.X; x++ { - yi := m.YOffset(x, y) - ci := m.COffset(x, y) - if m.Y[yi] != ym.Y[yi] { - t.Errorf("Err Y - found: %d expected: %d x: %d y: %d yi: %d", - m.Y[yi], ym.Y[yi], x, y, yi) - } - if m.Cb[ci] != ym.Cb[ci] { - t.Errorf("Err Cb - found: %d expected: %d x: %d y: %d ci: %d", - m.Cb[ci], ym.Cb[ci], x, y, ci) - } - if m.Cr[ci] != ym.Cr[ci] { - t.Errorf("Err Cr - found: %d expected: %d x: %d y: %d ci: %d", - m.Cr[ci], ym.Cr[ci], x, y, ci) - } - } - } - } -} - -func TestYCbCr(t *testing.T) { - rects := []image.Rectangle{ - image.Rect(0, 0, 16, 16), - image.Rect(1, 0, 16, 16), - image.Rect(0, 1, 16, 16), - image.Rect(1, 1, 16, 16), - image.Rect(1, 1, 15, 16), - image.Rect(1, 1, 16, 15), - image.Rect(1, 1, 15, 15), - image.Rect(2, 3, 14, 15), - image.Rect(7, 0, 7, 16), - image.Rect(0, 8, 16, 8), - image.Rect(0, 0, 10, 11), - image.Rect(5, 6, 16, 16), - image.Rect(7, 7, 8, 8), - image.Rect(7, 8, 8, 9), - image.Rect(8, 7, 9, 8), - image.Rect(8, 8, 9, 9), - image.Rect(7, 7, 17, 17), - image.Rect(8, 8, 17, 17), - image.Rect(9, 9, 17, 17), - image.Rect(10, 10, 17, 17), - } - subsampleRatios := []image.YCbCrSubsampleRatio{ - image.YCbCrSubsampleRatio444, - image.YCbCrSubsampleRatio422, - image.YCbCrSubsampleRatio420, - image.YCbCrSubsampleRatio440, - } - deltas := []image.Point{ - image.Pt(0, 0), - image.Pt(1000, 1001), - image.Pt(5001, -400), - image.Pt(-701, -801), - } - for _, r := range rects { - for _, subsampleRatio := range subsampleRatios { - for _, delta := range deltas { - testYCbCr(t, r, subsampleRatio, delta) - } - } - if testing.Short() { - break - } - } -} - -func testYCbCr(t *testing.T, r image.Rectangle, subsampleRatio image.YCbCrSubsampleRatio, delta image.Point) { - // Create a YCbCr image m, whose bounds are r translated by (delta.X, delta.Y). - r1 := r.Add(delta) - img := image.NewYCbCr(r1, subsampleRatio) - - // Initialize img's pixels. For 422 and 420 subsampling, some of the Cb and Cr elements - // will be set multiple times. That's OK. We just want to avoid a uniform image. - for y := r1.Min.Y; y < r1.Max.Y; y++ { - for x := r1.Min.X; x < r1.Max.X; x++ { - yi := img.YOffset(x, y) - ci := img.COffset(x, y) - img.Y[yi] = uint8(16*y + x) - img.Cb[ci] = uint8(y + 16*x) - img.Cr[ci] = uint8(y + 16*x) - } - } - - m := imageYCbCrToYCC(img) - - // Make various sub-images of m. - for y0 := delta.Y + 3; y0 < delta.Y+7; y0++ { - for y1 := delta.Y + 8; y1 < delta.Y+13; y1++ { - for x0 := delta.X + 3; x0 < delta.X+7; x0++ { - for x1 := delta.X + 8; x1 < delta.X+13; x1++ { - subRect := image.Rect(x0, y0, x1, y1) - sub := m.SubImage(subRect).(*ycc) - - // For each point in the sub-image's bounds, check that m.At(x, y) equals sub.At(x, y). - for y := sub.Rect.Min.Y; y < sub.Rect.Max.Y; y++ { - for x := sub.Rect.Min.X; x < sub.Rect.Max.X; x++ { - color0 := m.At(x, y).(color.YCbCr) - color1 := sub.At(x, y).(color.YCbCr) - if color0 != color1 { - t.Errorf("r=%v, subsampleRatio=%v, delta=%v, x=%d, y=%d, color0=%v, color1=%v", - r, subsampleRatio, delta, x, y, color0, color1) - return - } - } - } - } - } - } - } -} diff --git a/Godeps/_workspace/src/golang.org/x/image/tiff/buffer.go b/Godeps/_workspace/src/golang.org/x/image/tiff/buffer.go new file mode 100644 index 000000000..d1801be48 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/image/tiff/buffer.go @@ -0,0 +1,69 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package tiff + +import "io" + +// buffer buffers an io.Reader to satisfy io.ReaderAt. +type buffer struct { + r io.Reader + buf []byte +} + +// fill reads data from b.r until the buffer contains at least end bytes. +func (b *buffer) fill(end int) error { + m := len(b.buf) + if end > m { + if end > cap(b.buf) { + newcap := 1024 + for newcap < end { + newcap *= 2 + } + newbuf := make([]byte, end, newcap) + copy(newbuf, b.buf) + b.buf = newbuf + } else { + b.buf = b.buf[:end] + } + if n, err := io.ReadFull(b.r, b.buf[m:end]); err != nil { + end = m + n + b.buf = b.buf[:end] + return err + } + } + return nil +} + +func (b *buffer) ReadAt(p []byte, off int64) (int, error) { + o := int(off) + end := o + len(p) + if int64(end) != off+int64(len(p)) { + return 0, io.ErrUnexpectedEOF + } + + err := b.fill(end) + return copy(p, b.buf[o:end]), err +} + +// Slice returns a slice of the underlying buffer. The slice contains +// n bytes starting at offset off. +func (b *buffer) Slice(off, n int) ([]byte, error) { + end := off + n + if err := b.fill(end); err != nil { + return nil, err + } + return b.buf[off:end], nil +} + +// newReaderAt converts an io.Reader into an io.ReaderAt. +func newReaderAt(r io.Reader) io.ReaderAt { + if ra, ok := r.(io.ReaderAt); ok { + return ra + } + return &buffer{ + r: r, + buf: make([]byte, 0, 1024), + } +} diff --git a/Godeps/_workspace/src/golang.org/x/image/tiff/compress.go b/Godeps/_workspace/src/golang.org/x/image/tiff/compress.go new file mode 100644 index 000000000..3f176f00a --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/image/tiff/compress.go @@ -0,0 +1,58 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package tiff + +import ( + "bufio" + "io" +) + +type byteReader interface { + io.Reader + io.ByteReader +} + +// unpackBits decodes the PackBits-compressed data in src and returns the +// uncompressed data. +// +// The PackBits compression format is described in section 9 (p. 42) +// of the TIFF spec. +func unpackBits(r io.Reader) ([]byte, error) { + buf := make([]byte, 128) + dst := make([]byte, 0, 1024) + br, ok := r.(byteReader) + if !ok { + br = bufio.NewReader(r) + } + + for { + b, err := br.ReadByte() + if err != nil { + if err == io.EOF { + return dst, nil + } + return nil, err + } + code := int(int8(b)) + switch { + case code >= 0: + n, err := io.ReadFull(br, buf[:code+1]) + if err != nil { + return nil, err + } + dst = append(dst, buf[:n]...) + case code == -128: + // No-op. + default: + if b, err = br.ReadByte(); err != nil { + return nil, err + } + for j := 0; j < 1-code; j++ { + buf[j] = b + } + dst = append(dst, buf[:1-code]...) + } + } +} diff --git a/Godeps/_workspace/src/golang.org/x/image/tiff/consts.go b/Godeps/_workspace/src/golang.org/x/image/tiff/consts.go new file mode 100644 index 000000000..3c51a70be --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/image/tiff/consts.go @@ -0,0 +1,133 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package tiff + +// A tiff image file contains one or more images. The metadata +// of each image is contained in an Image File Directory (IFD), +// which contains entries of 12 bytes each and is described +// on page 14-16 of the specification. An IFD entry consists of +// +// - a tag, which describes the signification of the entry, +// - the data type and length of the entry, +// - the data itself or a pointer to it if it is more than 4 bytes. +// +// The presence of a length means that each IFD is effectively an array. + +const ( + leHeader = "II\x2A\x00" // Header for little-endian files. + beHeader = "MM\x00\x2A" // Header for big-endian files. + + ifdLen = 12 // Length of an IFD entry in bytes. +) + +// Data types (p. 14-16 of the spec). +const ( + dtByte = 1 + dtASCII = 2 + dtShort = 3 + dtLong = 4 + dtRational = 5 +) + +// The length of one instance of each data type in bytes. +var lengths = [...]uint32{0, 1, 1, 2, 4, 8} + +// Tags (see p. 28-41 of the spec). +const ( + tImageWidth = 256 + tImageLength = 257 + tBitsPerSample = 258 + tCompression = 259 + tPhotometricInterpretation = 262 + + tStripOffsets = 273 + tSamplesPerPixel = 277 + tRowsPerStrip = 278 + tStripByteCounts = 279 + + tTileWidth = 322 + tTileLength = 323 + tTileOffsets = 324 + tTileByteCounts = 325 + + tXResolution = 282 + tYResolution = 283 + tResolutionUnit = 296 + + tPredictor = 317 + tColorMap = 320 + tExtraSamples = 338 + tSampleFormat = 339 +) + +// Compression types (defined in various places in the spec and supplements). +const ( + cNone = 1 + cCCITT = 2 + cG3 = 3 // Group 3 Fax. + cG4 = 4 // Group 4 Fax. + cLZW = 5 + cJPEGOld = 6 // Superseded by cJPEG. + cJPEG = 7 + cDeflate = 8 // zlib compression. + cPackBits = 32773 + cDeflateOld = 32946 // Superseded by cDeflate. +) + +// Photometric interpretation values (see p. 37 of the spec). +const ( + pWhiteIsZero = 0 + pBlackIsZero = 1 + pRGB = 2 + pPaletted = 3 + pTransMask = 4 // transparency mask + pCMYK = 5 + pYCbCr = 6 + pCIELab = 8 +) + +// Values for the tPredictor tag (page 64-65 of the spec). +const ( + prNone = 1 + prHorizontal = 2 +) + +// Values for the tResolutionUnit tag (page 18). +const ( + resNone = 1 + resPerInch = 2 // Dots per inch. + resPerCM = 3 // Dots per centimeter. +) + +// imageMode represents the mode of the image. +type imageMode int + +const ( + mBilevel imageMode = iota + mPaletted + mGray + mGrayInvert + mRGB + mRGBA + mNRGBA +) + +// CompressionType describes the type of compression used in Options. +type CompressionType int + +const ( + Uncompressed CompressionType = iota + Deflate +) + +// specValue returns the compression type constant from the TIFF spec that +// is equivalent to c. +func (c CompressionType) specValue() uint32 { + switch c { + case Deflate: + return cDeflate + } + return cNone +} diff --git a/Godeps/_workspace/src/golang.org/x/image/tiff/lzw/reader.go b/Godeps/_workspace/src/golang.org/x/image/tiff/lzw/reader.go new file mode 100644 index 000000000..dc9f7dd1d --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/image/tiff/lzw/reader.go @@ -0,0 +1,277 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package lzw implements the Lempel-Ziv-Welch compressed data format, +// described in T. A. Welch, ``A Technique for High-Performance Data +// Compression'', Computer, 17(6) (June 1984), pp 8-19. +// +// In particular, it implements LZW as used by the TIFF file format, including +// an "off by one" algorithmic difference when compared to standard LZW. +package lzw + +/* +This file was branched from src/pkg/compress/lzw/reader.go in the +standard library. Differences from the original are marked with "NOTE". + +The tif_lzw.c file in the libtiff C library has this comment: + +---- +The 5.0 spec describes a different algorithm than Aldus +implements. Specifically, Aldus does code length transitions +one code earlier than should be done (for real LZW). +Earlier versions of this library implemented the correct +LZW algorithm, but emitted codes in a bit order opposite +to the TIFF spec. Thus, to maintain compatibility w/ Aldus +we interpret MSB-LSB ordered codes to be images written w/ +old versions of this library, but otherwise adhere to the +Aldus "off by one" algorithm. +---- + +The Go code doesn't read (invalid) TIFF files written by old versions of +libtiff, but the LZW algorithm in this package still differs from the one in +Go's standard package library to accomodate this "off by one" in valid TIFFs. +*/ + +import ( + "bufio" + "errors" + "fmt" + "io" +) + +// Order specifies the bit ordering in an LZW data stream. +type Order int + +const ( + // LSB means Least Significant Bits first, as used in the GIF file format. + LSB Order = iota + // MSB means Most Significant Bits first, as used in the TIFF and PDF + // file formats. + MSB +) + +const ( + maxWidth = 12 + decoderInvalidCode = 0xffff + flushBuffer = 1 << maxWidth +) + +// decoder is the state from which the readXxx method converts a byte +// stream into a code stream. +type decoder struct { + r io.ByteReader + bits uint32 + nBits uint + width uint + read func(*decoder) (uint16, error) // readLSB or readMSB + litWidth int // width in bits of literal codes + err error + + // The first 1<<litWidth codes are literal codes. + // The next two codes mean clear and EOF. + // Other valid codes are in the range [lo, hi] where lo := clear + 2, + // with the upper bound incrementing on each code seen. + // overflow is the code at which hi overflows the code width. NOTE: TIFF's LZW is "off by one". + // last is the most recently seen code, or decoderInvalidCode. + clear, eof, hi, overflow, last uint16 + + // Each code c in [lo, hi] expands to two or more bytes. For c != hi: + // suffix[c] is the last of these bytes. + // prefix[c] is the code for all but the last byte. + // This code can either be a literal code or another code in [lo, c). + // The c == hi case is a special case. + suffix [1 << maxWidth]uint8 + prefix [1 << maxWidth]uint16 + + // output is the temporary output buffer. + // Literal codes are accumulated from the start of the buffer. + // Non-literal codes decode to a sequence of suffixes that are first + // written right-to-left from the end of the buffer before being copied + // to the start of the buffer. + // It is flushed when it contains >= 1<<maxWidth bytes, + // so that there is always room to decode an entire code. + output [2 * 1 << maxWidth]byte + o int // write index into output + toRead []byte // bytes to return from Read +} + +// readLSB returns the next code for "Least Significant Bits first" data. +func (d *decoder) readLSB() (uint16, error) { + for d.nBits < d.width { + x, err := d.r.ReadByte() + if err != nil { + return 0, err + } + d.bits |= uint32(x) << d.nBits + d.nBits += 8 + } + code := uint16(d.bits & (1<<d.width - 1)) + d.bits >>= d.width + d.nBits -= d.width + return code, nil +} + +// readMSB returns the next code for "Most Significant Bits first" data. +func (d *decoder) readMSB() (uint16, error) { + for d.nBits < d.width { + x, err := d.r.ReadByte() + if err != nil { + return 0, err + } + d.bits |= uint32(x) << (24 - d.nBits) + d.nBits += 8 + } + code := uint16(d.bits >> (32 - d.width)) + d.bits <<= d.width + d.nBits -= d.width + return code, nil +} + +func (d *decoder) Read(b []byte) (int, error) { + for { + if len(d.toRead) > 0 { + n := copy(b, d.toRead) + d.toRead = d.toRead[n:] + return n, nil + } + if d.err != nil { + return 0, d.err + } + d.decode() + } +} + +// decode decompresses bytes from r and leaves them in d.toRead. +// read specifies how to decode bytes into codes. +// litWidth is the width in bits of literal codes. +func (d *decoder) decode() { + // Loop over the code stream, converting codes into decompressed bytes. + for { + code, err := d.read(d) + if err != nil { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + d.err = err + d.flush() + return + } + switch { + case code < d.clear: + // We have a literal code. + d.output[d.o] = uint8(code) + d.o++ + if d.last != decoderInvalidCode { + // Save what the hi code expands to. + d.suffix[d.hi] = uint8(code) + d.prefix[d.hi] = d.last + } + case code == d.clear: + d.width = 1 + uint(d.litWidth) + d.hi = d.eof + d.overflow = 1 << d.width + d.last = decoderInvalidCode + continue + case code == d.eof: + d.flush() + d.err = io.EOF + return + case code <= d.hi: + c, i := code, len(d.output)-1 + if code == d.hi { + // code == hi is a special case which expands to the last expansion + // followed by the head of the last expansion. To find the head, we walk + // the prefix chain until we find a literal code. + c = d.last + for c >= d.clear { + c = d.prefix[c] + } + d.output[i] = uint8(c) + i-- + c = d.last + } + // Copy the suffix chain into output and then write that to w. + for c >= d.clear { + d.output[i] = d.suffix[c] + i-- + c = d.prefix[c] + } + d.output[i] = uint8(c) + d.o += copy(d.output[d.o:], d.output[i:]) + if d.last != decoderInvalidCode { + // Save what the hi code expands to. + d.suffix[d.hi] = uint8(c) + d.prefix[d.hi] = d.last + } + default: + d.err = errors.New("lzw: invalid code") + d.flush() + return + } + d.last, d.hi = code, d.hi+1 + if d.hi+1 >= d.overflow { // NOTE: the "+1" is where TIFF's LZW differs from the standard algorithm. + if d.width == maxWidth { + d.last = decoderInvalidCode + } else { + d.width++ + d.overflow <<= 1 + } + } + if d.o >= flushBuffer { + d.flush() + return + } + } +} + +func (d *decoder) flush() { + d.toRead = d.output[:d.o] + d.o = 0 +} + +var errClosed = errors.New("lzw: reader/writer is closed") + +func (d *decoder) Close() error { + d.err = errClosed // in case any Reads come along + return nil +} + +// NewReader creates a new io.ReadCloser. +// Reads from the returned io.ReadCloser read and decompress data from r. +// If r does not also implement io.ByteReader, +// the decompressor may read more data than necessary from r. +// It is the caller's responsibility to call Close on the ReadCloser when +// finished reading. +// The number of bits to use for literal codes, litWidth, must be in the +// range [2,8] and is typically 8. It must equal the litWidth +// used during compression. +func NewReader(r io.Reader, order Order, litWidth int) io.ReadCloser { + d := new(decoder) + switch order { + case LSB: + d.read = (*decoder).readLSB + case MSB: + d.read = (*decoder).readMSB + default: + d.err = errors.New("lzw: unknown order") + return d + } + if litWidth < 2 || 8 < litWidth { + d.err = fmt.Errorf("lzw: litWidth %d out of range", litWidth) + return d + } + if br, ok := r.(io.ByteReader); ok { + d.r = br + } else { + d.r = bufio.NewReader(r) + } + d.litWidth = litWidth + d.width = 1 + uint(litWidth) + d.clear = uint16(1) << uint(litWidth) + d.eof, d.hi = d.clear+1, d.clear+1 + d.overflow = uint16(1) << d.width + d.last = decoderInvalidCode + + return d +} diff --git a/Godeps/_workspace/src/golang.org/x/image/tiff/reader.go b/Godeps/_workspace/src/golang.org/x/image/tiff/reader.go new file mode 100644 index 000000000..714e3dda7 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/image/tiff/reader.go @@ -0,0 +1,681 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package tiff implements a TIFF image decoder and encoder. +// +// The TIFF specification is at http://partners.adobe.com/public/developer/en/tiff/TIFF6.pdf +package tiff + +import ( + "compress/zlib" + "encoding/binary" + "fmt" + "image" + "image/color" + "io" + "io/ioutil" + "math" + + "golang.org/x/image/tiff/lzw" +) + +// A FormatError reports that the input is not a valid TIFF image. +type FormatError string + +func (e FormatError) Error() string { + return "tiff: invalid format: " + string(e) +} + +// An UnsupportedError reports that the input uses a valid but +// unimplemented feature. +type UnsupportedError string + +func (e UnsupportedError) Error() string { + return "tiff: unsupported feature: " + string(e) +} + +// An InternalError reports that an internal error was encountered. +type InternalError string + +func (e InternalError) Error() string { + return "tiff: internal error: " + string(e) +} + +var errNoPixels = FormatError("not enough pixel data") + +type decoder struct { + r io.ReaderAt + byteOrder binary.ByteOrder + config image.Config + mode imageMode + bpp uint + features map[int][]uint + palette []color.Color + + buf []byte + off int // Current offset in buf. + v uint32 // Buffer value for reading with arbitrary bit depths. + nbits uint // Remaining number of bits in v. +} + +// firstVal returns the first uint of the features entry with the given tag, +// or 0 if the tag does not exist. +func (d *decoder) firstVal(tag int) uint { + f := d.features[tag] + if len(f) == 0 { + return 0 + } + return f[0] +} + +// ifdUint decodes the IFD entry in p, which must be of the Byte, Short +// or Long type, and returns the decoded uint values. +func (d *decoder) ifdUint(p []byte) (u []uint, err error) { + var raw []byte + if len(p) < ifdLen { + return nil, FormatError("bad IFD entry") + } + + datatype := d.byteOrder.Uint16(p[2:4]) + if dt := int(datatype); dt <= 0 || dt >= len(lengths) { + return nil, UnsupportedError("IFD entry datatype") + } + + count := d.byteOrder.Uint32(p[4:8]) + if count > math.MaxInt32/lengths[datatype] { + return nil, FormatError("IFD data too large") + } + if datalen := lengths[datatype] * count; datalen > 4 { + // The IFD contains a pointer to the real value. + raw = make([]byte, datalen) + _, err = d.r.ReadAt(raw, int64(d.byteOrder.Uint32(p[8:12]))) + } else { + raw = p[8 : 8+datalen] + } + if err != nil { + return nil, err + } + + u = make([]uint, count) + switch datatype { + case dtByte: + for i := uint32(0); i < count; i++ { + u[i] = uint(raw[i]) + } + case dtShort: + for i := uint32(0); i < count; i++ { + u[i] = uint(d.byteOrder.Uint16(raw[2*i : 2*(i+1)])) + } + case dtLong: + for i := uint32(0); i < count; i++ { + u[i] = uint(d.byteOrder.Uint32(raw[4*i : 4*(i+1)])) + } + default: + return nil, UnsupportedError("data type") + } + return u, nil +} + +// parseIFD decides whether the the IFD entry in p is "interesting" and +// stows away the data in the decoder. +func (d *decoder) parseIFD(p []byte) error { + tag := d.byteOrder.Uint16(p[0:2]) + switch tag { + case tBitsPerSample, + tExtraSamples, + tPhotometricInterpretation, + tCompression, + tPredictor, + tStripOffsets, + tStripByteCounts, + tRowsPerStrip, + tTileWidth, + tTileLength, + tTileOffsets, + tTileByteCounts, + tImageLength, + tImageWidth: + val, err := d.ifdUint(p) + if err != nil { + return err + } + d.features[int(tag)] = val + case tColorMap: + val, err := d.ifdUint(p) + if err != nil { + return err + } + numcolors := len(val) / 3 + if len(val)%3 != 0 || numcolors <= 0 || numcolors > 256 { + return FormatError("bad ColorMap length") + } + d.palette = make([]color.Color, numcolors) + for i := 0; i < numcolors; i++ { + d.palette[i] = color.RGBA64{ + uint16(val[i]), + uint16(val[i+numcolors]), + uint16(val[i+2*numcolors]), + 0xffff, + } + } + case tSampleFormat: + // Page 27 of the spec: If the SampleFormat is present and + // the value is not 1 [= unsigned integer data], a Baseline + // TIFF reader that cannot handle the SampleFormat value + // must terminate the import process gracefully. + val, err := d.ifdUint(p) + if err != nil { + return err + } + for _, v := range val { + if v != 1 { + return UnsupportedError("sample format") + } + } + } + return nil +} + +// readBits reads n bits from the internal buffer starting at the current offset. +func (d *decoder) readBits(n uint) (v uint32, ok bool) { + for d.nbits < n { + d.v <<= 8 + if d.off >= len(d.buf) { + return 0, false + } + d.v |= uint32(d.buf[d.off]) + d.off++ + d.nbits += 8 + } + d.nbits -= n + rv := d.v >> d.nbits + d.v &^= rv << d.nbits + return rv, true +} + +// flushBits discards the unread bits in the buffer used by readBits. +// It is used at the end of a line. +func (d *decoder) flushBits() { + d.v = 0 + d.nbits = 0 +} + +// minInt returns the smaller of x or y. +func minInt(a, b int) int { + if a <= b { + return a + } + return b +} + +// decode decodes the raw data of an image. +// It reads from d.buf and writes the strip or tile into dst. +func (d *decoder) decode(dst image.Image, xmin, ymin, xmax, ymax int) error { + d.off = 0 + + // Apply horizontal predictor if necessary. + // In this case, p contains the color difference to the preceding pixel. + // See page 64-65 of the spec. + if d.firstVal(tPredictor) == prHorizontal { + switch d.bpp { + case 16: + var off int + n := 2 * len(d.features[tBitsPerSample]) // bytes per sample times samples per pixel + for y := ymin; y < ymax; y++ { + off += n + for x := 0; x < (xmax-xmin-1)*n; x += 2 { + if off+2 > len(d.buf) { + return errNoPixels + } + v0 := d.byteOrder.Uint16(d.buf[off-n : off-n+2]) + v1 := d.byteOrder.Uint16(d.buf[off : off+2]) + d.byteOrder.PutUint16(d.buf[off:off+2], v1+v0) + off += 2 + } + } + case 8: + var off int + n := 1 * len(d.features[tBitsPerSample]) // bytes per sample times samples per pixel + for y := ymin; y < ymax; y++ { + off += n + for x := 0; x < (xmax-xmin-1)*n; x++ { + if off >= len(d.buf) { + return errNoPixels + } + d.buf[off] += d.buf[off-n] + off++ + } + } + case 1: + return UnsupportedError("horizontal predictor with 1 BitsPerSample") + } + } + + rMaxX := minInt(xmax, dst.Bounds().Max.X) + rMaxY := minInt(ymax, dst.Bounds().Max.Y) + switch d.mode { + case mGray, mGrayInvert: + if d.bpp == 16 { + img := dst.(*image.Gray16) + for y := ymin; y < rMaxY; y++ { + for x := xmin; x < rMaxX; x++ { + if d.off+2 > len(d.buf) { + return errNoPixels + } + v := d.byteOrder.Uint16(d.buf[d.off : d.off+2]) + d.off += 2 + if d.mode == mGrayInvert { + v = 0xffff - v + } + img.SetGray16(x, y, color.Gray16{v}) + } + } + } else { + img := dst.(*image.Gray) + max := uint32((1 << d.bpp) - 1) + for y := ymin; y < rMaxY; y++ { + for x := xmin; x < rMaxX; x++ { + v, ok := d.readBits(d.bpp) + if !ok { + return errNoPixels + } + v = v * 0xff / max + if d.mode == mGrayInvert { + v = 0xff - v + } + img.SetGray(x, y, color.Gray{uint8(v)}) + } + d.flushBits() + } + } + case mPaletted: + img := dst.(*image.Paletted) + for y := ymin; y < rMaxY; y++ { + for x := xmin; x < rMaxX; x++ { + v, ok := d.readBits(d.bpp) + if !ok { + return errNoPixels + } + img.SetColorIndex(x, y, uint8(v)) + } + d.flushBits() + } + case mRGB: + if d.bpp == 16 { + img := dst.(*image.RGBA64) + for y := ymin; y < rMaxY; y++ { + for x := xmin; x < rMaxX; x++ { + if d.off+6 > len(d.buf) { + return errNoPixels + } + r := d.byteOrder.Uint16(d.buf[d.off+0 : d.off+2]) + g := d.byteOrder.Uint16(d.buf[d.off+2 : d.off+4]) + b := d.byteOrder.Uint16(d.buf[d.off+4 : d.off+6]) + d.off += 6 + img.SetRGBA64(x, y, color.RGBA64{r, g, b, 0xffff}) + } + } + } else { + img := dst.(*image.RGBA) + for y := ymin; y < rMaxY; y++ { + min := img.PixOffset(xmin, y) + max := img.PixOffset(rMaxX, y) + off := (y - ymin) * (xmax - xmin) * 3 + for i := min; i < max; i += 4 { + if off+3 > len(d.buf) { + return errNoPixels + } + img.Pix[i+0] = d.buf[off+0] + img.Pix[i+1] = d.buf[off+1] + img.Pix[i+2] = d.buf[off+2] + img.Pix[i+3] = 0xff + off += 3 + } + } + } + case mNRGBA: + if d.bpp == 16 { + img := dst.(*image.NRGBA64) + for y := ymin; y < rMaxY; y++ { + for x := xmin; x < rMaxX; x++ { + if d.off+8 > len(d.buf) { + return errNoPixels + } + r := d.byteOrder.Uint16(d.buf[d.off+0 : d.off+2]) + g := d.byteOrder.Uint16(d.buf[d.off+2 : d.off+4]) + b := d.byteOrder.Uint16(d.buf[d.off+4 : d.off+6]) + a := d.byteOrder.Uint16(d.buf[d.off+6 : d.off+8]) + d.off += 8 + img.SetNRGBA64(x, y, color.NRGBA64{r, g, b, a}) + } + } + } else { + img := dst.(*image.NRGBA) + for y := ymin; y < rMaxY; y++ { + min := img.PixOffset(xmin, y) + max := img.PixOffset(rMaxX, y) + i0, i1 := (y-ymin)*(xmax-xmin)*4, (y-ymin+1)*(xmax-xmin)*4 + if i1 > len(d.buf) { + return errNoPixels + } + copy(img.Pix[min:max], d.buf[i0:i1]) + } + } + case mRGBA: + if d.bpp == 16 { + img := dst.(*image.RGBA64) + for y := ymin; y < rMaxY; y++ { + for x := xmin; x < rMaxX; x++ { + if d.off+8 > len(d.buf) { + return errNoPixels + } + r := d.byteOrder.Uint16(d.buf[d.off+0 : d.off+2]) + g := d.byteOrder.Uint16(d.buf[d.off+2 : d.off+4]) + b := d.byteOrder.Uint16(d.buf[d.off+4 : d.off+6]) + a := d.byteOrder.Uint16(d.buf[d.off+6 : d.off+8]) + d.off += 8 + img.SetRGBA64(x, y, color.RGBA64{r, g, b, a}) + } + } + } else { + img := dst.(*image.RGBA) + for y := ymin; y < rMaxY; y++ { + min := img.PixOffset(xmin, y) + max := img.PixOffset(rMaxX, y) + i0, i1 := (y-ymin)*(xmax-xmin)*4, (y-ymin+1)*(xmax-xmin)*4 + if i1 > len(d.buf) { + return errNoPixels + } + copy(img.Pix[min:max], d.buf[i0:i1]) + } + } + } + + return nil +} + +func newDecoder(r io.Reader) (*decoder, error) { + d := &decoder{ + r: newReaderAt(r), + features: make(map[int][]uint), + } + + p := make([]byte, 8) + if _, err := d.r.ReadAt(p, 0); err != nil { + return nil, err + } + switch string(p[0:4]) { + case leHeader: + d.byteOrder = binary.LittleEndian + case beHeader: + d.byteOrder = binary.BigEndian + default: + return nil, FormatError("malformed header") + } + + ifdOffset := int64(d.byteOrder.Uint32(p[4:8])) + + // The first two bytes contain the number of entries (12 bytes each). + if _, err := d.r.ReadAt(p[0:2], ifdOffset); err != nil { + return nil, err + } + numItems := int(d.byteOrder.Uint16(p[0:2])) + + // All IFD entries are read in one chunk. + p = make([]byte, ifdLen*numItems) + if _, err := d.r.ReadAt(p, ifdOffset+2); err != nil { + return nil, err + } + + for i := 0; i < len(p); i += ifdLen { + if err := d.parseIFD(p[i : i+ifdLen]); err != nil { + return nil, err + } + } + + d.config.Width = int(d.firstVal(tImageWidth)) + d.config.Height = int(d.firstVal(tImageLength)) + + if _, ok := d.features[tBitsPerSample]; !ok { + return nil, FormatError("BitsPerSample tag missing") + } + d.bpp = d.firstVal(tBitsPerSample) + switch d.bpp { + case 0: + return nil, FormatError("BitsPerSample must not be 0") + case 1, 8, 16: + // Nothing to do, these are accepted by this implementation. + default: + return nil, UnsupportedError(fmt.Sprintf("BitsPerSample of %v", d.bpp)) + } + + // Determine the image mode. + switch d.firstVal(tPhotometricInterpretation) { + case pRGB: + if d.bpp == 16 { + for _, b := range d.features[tBitsPerSample] { + if b != 16 { + return nil, FormatError("wrong number of samples for 16bit RGB") + } + } + } else { + for _, b := range d.features[tBitsPerSample] { + if b != 8 { + return nil, FormatError("wrong number of samples for 8bit RGB") + } + } + } + // RGB images normally have 3 samples per pixel. + // If there are more, ExtraSamples (p. 31-32 of the spec) + // gives their meaning (usually an alpha channel). + // + // This implementation does not support extra samples + // of an unspecified type. + switch len(d.features[tBitsPerSample]) { + case 3: + d.mode = mRGB + if d.bpp == 16 { + d.config.ColorModel = color.RGBA64Model + } else { + d.config.ColorModel = color.RGBAModel + } + case 4: + switch d.firstVal(tExtraSamples) { + case 1: + d.mode = mRGBA + if d.bpp == 16 { + d.config.ColorModel = color.RGBA64Model + } else { + d.config.ColorModel = color.RGBAModel + } + case 2: + d.mode = mNRGBA + if d.bpp == 16 { + d.config.ColorModel = color.NRGBA64Model + } else { + d.config.ColorModel = color.NRGBAModel + } + default: + return nil, FormatError("wrong number of samples for RGB") + } + default: + return nil, FormatError("wrong number of samples for RGB") + } + case pPaletted: + d.mode = mPaletted + d.config.ColorModel = color.Palette(d.palette) + case pWhiteIsZero: + d.mode = mGrayInvert + if d.bpp == 16 { + d.config.ColorModel = color.Gray16Model + } else { + d.config.ColorModel = color.GrayModel + } + case pBlackIsZero: + d.mode = mGray + if d.bpp == 16 { + d.config.ColorModel = color.Gray16Model + } else { + d.config.ColorModel = color.GrayModel + } + default: + return nil, UnsupportedError("color model") + } + + return d, nil +} + +// DecodeConfig returns the color model and dimensions of a TIFF image without +// decoding the entire image. +func DecodeConfig(r io.Reader) (image.Config, error) { + d, err := newDecoder(r) + if err != nil { + return image.Config{}, err + } + return d.config, nil +} + +// Decode reads a TIFF image from r and returns it as an image.Image. +// The type of Image returned depends on the contents of the TIFF. +func Decode(r io.Reader) (img image.Image, err error) { + d, err := newDecoder(r) + if err != nil { + return + } + + blockPadding := false + blockWidth := d.config.Width + blockHeight := d.config.Height + blocksAcross := 1 + blocksDown := 1 + + if d.config.Width == 0 { + blocksAcross = 0 + } + if d.config.Height == 0 { + blocksDown = 0 + } + + var blockOffsets, blockCounts []uint + + if int(d.firstVal(tTileWidth)) != 0 { + blockPadding = true + + blockWidth = int(d.firstVal(tTileWidth)) + blockHeight = int(d.firstVal(tTileLength)) + + if blockWidth != 0 { + blocksAcross = (d.config.Width + blockWidth - 1) / blockWidth + } + if blockHeight != 0 { + blocksDown = (d.config.Height + blockHeight - 1) / blockHeight + } + + blockCounts = d.features[tTileByteCounts] + blockOffsets = d.features[tTileOffsets] + + } else { + if int(d.firstVal(tRowsPerStrip)) != 0 { + blockHeight = int(d.firstVal(tRowsPerStrip)) + } + + if blockHeight != 0 { + blocksDown = (d.config.Height + blockHeight - 1) / blockHeight + } + + blockOffsets = d.features[tStripOffsets] + blockCounts = d.features[tStripByteCounts] + } + + // Check if we have the right number of strips/tiles, offsets and counts. + if n := blocksAcross * blocksDown; len(blockOffsets) < n || len(blockCounts) < n { + return nil, FormatError("inconsistent header") + } + + imgRect := image.Rect(0, 0, d.config.Width, d.config.Height) + switch d.mode { + case mGray, mGrayInvert: + if d.bpp == 16 { + img = image.NewGray16(imgRect) + } else { + img = image.NewGray(imgRect) + } + case mPaletted: + img = image.NewPaletted(imgRect, d.palette) + case mNRGBA: + if d.bpp == 16 { + img = image.NewNRGBA64(imgRect) + } else { + img = image.NewNRGBA(imgRect) + } + case mRGB, mRGBA: + if d.bpp == 16 { + img = image.NewRGBA64(imgRect) + } else { + img = image.NewRGBA(imgRect) + } + } + + for i := 0; i < blocksAcross; i++ { + blkW := blockWidth + if !blockPadding && i == blocksAcross-1 && d.config.Width%blockWidth != 0 { + blkW = d.config.Width % blockWidth + } + for j := 0; j < blocksDown; j++ { + blkH := blockHeight + if !blockPadding && j == blocksDown-1 && d.config.Height%blockHeight != 0 { + blkH = d.config.Height % blockHeight + } + offset := int64(blockOffsets[j*blocksAcross+i]) + n := int64(blockCounts[j*blocksAcross+i]) + switch d.firstVal(tCompression) { + + // According to the spec, Compression does not have a default value, + // but some tools interpret a missing Compression value as none so we do + // the same. + case cNone, 0: + if b, ok := d.r.(*buffer); ok { + d.buf, err = b.Slice(int(offset), int(n)) + } else { + d.buf = make([]byte, n) + _, err = d.r.ReadAt(d.buf, offset) + } + case cLZW: + r := lzw.NewReader(io.NewSectionReader(d.r, offset, n), lzw.MSB, 8) + d.buf, err = ioutil.ReadAll(r) + r.Close() + case cDeflate, cDeflateOld: + var r io.ReadCloser + r, err = zlib.NewReader(io.NewSectionReader(d.r, offset, n)) + if err != nil { + return nil, err + } + d.buf, err = ioutil.ReadAll(r) + r.Close() + case cPackBits: + d.buf, err = unpackBits(io.NewSectionReader(d.r, offset, n)) + default: + err = UnsupportedError(fmt.Sprintf("compression value %d", d.firstVal(tCompression))) + } + if err != nil { + return nil, err + } + + xmin := i * blockWidth + ymin := j * blockHeight + xmax := xmin + blkW + ymax := ymin + blkH + err = d.decode(img, xmin, ymin, xmax, ymax) + if err != nil { + return nil, err + } + } + } + return +} + +func init() { + image.RegisterFormat("tiff", leHeader, Decode, DecodeConfig) + image.RegisterFormat("tiff", beHeader, Decode, DecodeConfig) +} diff --git a/Godeps/_workspace/src/golang.org/x/image/tiff/writer.go b/Godeps/_workspace/src/golang.org/x/image/tiff/writer.go new file mode 100644 index 000000000..c8a01cea7 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/image/tiff/writer.go @@ -0,0 +1,438 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package tiff + +import ( + "bytes" + "compress/zlib" + "encoding/binary" + "image" + "io" + "sort" +) + +// The TIFF format allows to choose the order of the different elements freely. +// The basic structure of a TIFF file written by this package is: +// +// 1. Header (8 bytes). +// 2. Image data. +// 3. Image File Directory (IFD). +// 4. "Pointer area" for larger entries in the IFD. + +// We only write little-endian TIFF files. +var enc = binary.LittleEndian + +// An ifdEntry is a single entry in an Image File Directory. +// A value of type dtRational is composed of two 32-bit values, +// thus data contains two uints (numerator and denominator) for a single number. +type ifdEntry struct { + tag int + datatype int + data []uint32 +} + +func (e ifdEntry) putData(p []byte) { + for _, d := range e.data { + switch e.datatype { + case dtByte, dtASCII: + p[0] = byte(d) + p = p[1:] + case dtShort: + enc.PutUint16(p, uint16(d)) + p = p[2:] + case dtLong, dtRational: + enc.PutUint32(p, uint32(d)) + p = p[4:] + } + } +} + +type byTag []ifdEntry + +func (d byTag) Len() int { return len(d) } +func (d byTag) Less(i, j int) bool { return d[i].tag < d[j].tag } +func (d byTag) Swap(i, j int) { d[i], d[j] = d[j], d[i] } + +func encodeGray(w io.Writer, pix []uint8, dx, dy, stride int, predictor bool) error { + if !predictor { + return writePix(w, pix, dy, dx, stride) + } + buf := make([]byte, dx) + for y := 0; y < dy; y++ { + min := y*stride + 0 + max := y*stride + dx + off := 0 + var v0 uint8 + for i := min; i < max; i++ { + v1 := pix[i] + buf[off] = v1 - v0 + v0 = v1 + off++ + } + if _, err := w.Write(buf); err != nil { + return err + } + } + return nil +} + +func encodeGray16(w io.Writer, pix []uint8, dx, dy, stride int, predictor bool) error { + buf := make([]byte, dx*2) + for y := 0; y < dy; y++ { + min := y*stride + 0 + max := y*stride + dx*2 + off := 0 + var v0 uint16 + for i := min; i < max; i += 2 { + // An image.Gray16's Pix is in big-endian order. + v1 := uint16(pix[i])<<8 | uint16(pix[i+1]) + if predictor { + v0, v1 = v1, v1-v0 + } + // We only write little-endian TIFF files. + buf[off+0] = byte(v1) + buf[off+1] = byte(v1 >> 8) + off += 2 + } + if _, err := w.Write(buf); err != nil { + return err + } + } + return nil +} + +func encodeRGBA(w io.Writer, pix []uint8, dx, dy, stride int, predictor bool) error { + if !predictor { + return writePix(w, pix, dy, dx*4, stride) + } + buf := make([]byte, dx*4) + for y := 0; y < dy; y++ { + min := y*stride + 0 + max := y*stride + dx*4 + off := 0 + var r0, g0, b0, a0 uint8 + for i := min; i < max; i += 4 { + r1, g1, b1, a1 := pix[i+0], pix[i+1], pix[i+2], pix[i+3] + buf[off+0] = r1 - r0 + buf[off+1] = g1 - g0 + buf[off+2] = b1 - b0 + buf[off+3] = a1 - a0 + off += 4 + r0, g0, b0, a0 = r1, g1, b1, a1 + } + if _, err := w.Write(buf); err != nil { + return err + } + } + return nil +} + +func encodeRGBA64(w io.Writer, pix []uint8, dx, dy, stride int, predictor bool) error { + buf := make([]byte, dx*8) + for y := 0; y < dy; y++ { + min := y*stride + 0 + max := y*stride + dx*8 + off := 0 + var r0, g0, b0, a0 uint16 + for i := min; i < max; i += 8 { + // An image.RGBA64's Pix is in big-endian order. + r1 := uint16(pix[i+0])<<8 | uint16(pix[i+1]) + g1 := uint16(pix[i+2])<<8 | uint16(pix[i+3]) + b1 := uint16(pix[i+4])<<8 | uint16(pix[i+5]) + a1 := uint16(pix[i+6])<<8 | uint16(pix[i+7]) + if predictor { + r0, r1 = r1, r1-r0 + g0, g1 = g1, g1-g0 + b0, b1 = b1, b1-b0 + a0, a1 = a1, a1-a0 + } + // We only write little-endian TIFF files. + buf[off+0] = byte(r1) + buf[off+1] = byte(r1 >> 8) + buf[off+2] = byte(g1) + buf[off+3] = byte(g1 >> 8) + buf[off+4] = byte(b1) + buf[off+5] = byte(b1 >> 8) + buf[off+6] = byte(a1) + buf[off+7] = byte(a1 >> 8) + off += 8 + } + if _, err := w.Write(buf); err != nil { + return err + } + } + return nil +} + +func encode(w io.Writer, m image.Image, predictor bool) error { + bounds := m.Bounds() + buf := make([]byte, 4*bounds.Dx()) + for y := bounds.Min.Y; y < bounds.Max.Y; y++ { + off := 0 + if predictor { + var r0, g0, b0, a0 uint8 + for x := bounds.Min.X; x < bounds.Max.X; x++ { + r, g, b, a := m.At(x, y).RGBA() + r1 := uint8(r >> 8) + g1 := uint8(g >> 8) + b1 := uint8(b >> 8) + a1 := uint8(a >> 8) + buf[off+0] = r1 - r0 + buf[off+1] = g1 - g0 + buf[off+2] = b1 - b0 + buf[off+3] = a1 - a0 + off += 4 + r0, g0, b0, a0 = r1, g1, b1, a1 + } + } else { + for x := bounds.Min.X; x < bounds.Max.X; x++ { + r, g, b, a := m.At(x, y).RGBA() + buf[off+0] = uint8(r >> 8) + buf[off+1] = uint8(g >> 8) + buf[off+2] = uint8(b >> 8) + buf[off+3] = uint8(a >> 8) + off += 4 + } + } + if _, err := w.Write(buf); err != nil { + return err + } + } + return nil +} + +// writePix writes the internal byte array of an image to w. It is less general +// but much faster then encode. writePix is used when pix directly +// corresponds to one of the TIFF image types. +func writePix(w io.Writer, pix []byte, nrows, length, stride int) error { + if length == stride { + _, err := w.Write(pix[:nrows*length]) + return err + } + for ; nrows > 0; nrows-- { + if _, err := w.Write(pix[:length]); err != nil { + return err + } + pix = pix[stride:] + } + return nil +} + +func writeIFD(w io.Writer, ifdOffset int, d []ifdEntry) error { + var buf [ifdLen]byte + // Make space for "pointer area" containing IFD entry data + // longer than 4 bytes. + parea := make([]byte, 1024) + pstart := ifdOffset + ifdLen*len(d) + 6 + var o int // Current offset in parea. + + // The IFD has to be written with the tags in ascending order. + sort.Sort(byTag(d)) + + // Write the number of entries in this IFD. + if err := binary.Write(w, enc, uint16(len(d))); err != nil { + return err + } + for _, ent := range d { + enc.PutUint16(buf[0:2], uint16(ent.tag)) + enc.PutUint16(buf[2:4], uint16(ent.datatype)) + count := uint32(len(ent.data)) + if ent.datatype == dtRational { + count /= 2 + } + enc.PutUint32(buf[4:8], count) + datalen := int(count * lengths[ent.datatype]) + if datalen <= 4 { + ent.putData(buf[8:12]) + } else { + if (o + datalen) > len(parea) { + newlen := len(parea) + 1024 + for (o + datalen) > newlen { + newlen += 1024 + } + newarea := make([]byte, newlen) + copy(newarea, parea) + parea = newarea + } + ent.putData(parea[o : o+datalen]) + enc.PutUint32(buf[8:12], uint32(pstart+o)) + o += datalen + } + if _, err := w.Write(buf[:]); err != nil { + return err + } + } + // The IFD ends with the offset of the next IFD in the file, + // or zero if it is the last one (page 14). + if err := binary.Write(w, enc, uint32(0)); err != nil { + return err + } + _, err := w.Write(parea[:o]) + return err +} + +// Options are the encoding parameters. +type Options struct { + // Compression is the type of compression used. + Compression CompressionType + // Predictor determines whether a differencing predictor is used; + // if true, instead of each pixel's color, the color difference to the + // preceding one is saved. This improves the compression for certain + // types of images and compressors. For example, it works well for + // photos with Deflate compression. + Predictor bool +} + +// Encode writes the image m to w. opt determines the options used for +// encoding, such as the compression type. If opt is nil, an uncompressed +// image is written. +func Encode(w io.Writer, m image.Image, opt *Options) error { + d := m.Bounds().Size() + + compression := uint32(cNone) + predictor := false + if opt != nil { + compression = opt.Compression.specValue() + // The predictor field is only used with LZW. See page 64 of the spec. + predictor = opt.Predictor && compression == cLZW + } + + _, err := io.WriteString(w, leHeader) + if err != nil { + return err + } + + // Compressed data is written into a buffer first, so that we + // know the compressed size. + var buf bytes.Buffer + // dst holds the destination for the pixel data of the image -- + // either w or a writer to buf. + var dst io.Writer + // imageLen is the length of the pixel data in bytes. + // The offset of the IFD is imageLen + 8 header bytes. + var imageLen int + + switch compression { + case cNone: + dst = w + // Write IFD offset before outputting pixel data. + switch m.(type) { + case *image.Paletted: + imageLen = d.X * d.Y * 1 + case *image.Gray: + imageLen = d.X * d.Y * 1 + case *image.Gray16: + imageLen = d.X * d.Y * 2 + case *image.RGBA64: + imageLen = d.X * d.Y * 8 + case *image.NRGBA64: + imageLen = d.X * d.Y * 8 + default: + imageLen = d.X * d.Y * 4 + } + err = binary.Write(w, enc, uint32(imageLen+8)) + if err != nil { + return err + } + case cDeflate: + dst = zlib.NewWriter(&buf) + } + + pr := uint32(prNone) + photometricInterpretation := uint32(pRGB) + samplesPerPixel := uint32(4) + bitsPerSample := []uint32{8, 8, 8, 8} + extraSamples := uint32(0) + colorMap := []uint32{} + + if predictor { + pr = prHorizontal + } + switch m := m.(type) { + case *image.Paletted: + photometricInterpretation = pPaletted + samplesPerPixel = 1 + bitsPerSample = []uint32{8} + colorMap = make([]uint32, 256*3) + for i := 0; i < 256 && i < len(m.Palette); i++ { + r, g, b, _ := m.Palette[i].RGBA() + colorMap[i+0*256] = uint32(r) + colorMap[i+1*256] = uint32(g) + colorMap[i+2*256] = uint32(b) + } + err = encodeGray(dst, m.Pix, d.X, d.Y, m.Stride, predictor) + case *image.Gray: + photometricInterpretation = pBlackIsZero + samplesPerPixel = 1 + bitsPerSample = []uint32{8} + err = encodeGray(dst, m.Pix, d.X, d.Y, m.Stride, predictor) + case *image.Gray16: + photometricInterpretation = pBlackIsZero + samplesPerPixel = 1 + bitsPerSample = []uint32{16} + err = encodeGray16(dst, m.Pix, d.X, d.Y, m.Stride, predictor) + case *image.NRGBA: + extraSamples = 2 // Unassociated alpha. + err = encodeRGBA(dst, m.Pix, d.X, d.Y, m.Stride, predictor) + case *image.NRGBA64: + extraSamples = 2 // Unassociated alpha. + bitsPerSample = []uint32{16, 16, 16, 16} + err = encodeRGBA64(dst, m.Pix, d.X, d.Y, m.Stride, predictor) + case *image.RGBA: + extraSamples = 1 // Associated alpha. + err = encodeRGBA(dst, m.Pix, d.X, d.Y, m.Stride, predictor) + case *image.RGBA64: + extraSamples = 1 // Associated alpha. + bitsPerSample = []uint32{16, 16, 16, 16} + err = encodeRGBA64(dst, m.Pix, d.X, d.Y, m.Stride, predictor) + default: + extraSamples = 1 // Associated alpha. + err = encode(dst, m, predictor) + } + if err != nil { + return err + } + + if compression != cNone { + if err = dst.(io.Closer).Close(); err != nil { + return err + } + imageLen = buf.Len() + if err = binary.Write(w, enc, uint32(imageLen+8)); err != nil { + return err + } + if _, err = buf.WriteTo(w); err != nil { + return err + } + } + + ifd := []ifdEntry{ + {tImageWidth, dtShort, []uint32{uint32(d.X)}}, + {tImageLength, dtShort, []uint32{uint32(d.Y)}}, + {tBitsPerSample, dtShort, bitsPerSample}, + {tCompression, dtShort, []uint32{compression}}, + {tPhotometricInterpretation, dtShort, []uint32{photometricInterpretation}}, + {tStripOffsets, dtLong, []uint32{8}}, + {tSamplesPerPixel, dtShort, []uint32{samplesPerPixel}}, + {tRowsPerStrip, dtShort, []uint32{uint32(d.Y)}}, + {tStripByteCounts, dtLong, []uint32{uint32(imageLen)}}, + // There is currently no support for storing the image + // resolution, so give a bogus value of 72x72 dpi. + {tXResolution, dtRational, []uint32{72, 1}}, + {tYResolution, dtRational, []uint32{72, 1}}, + {tResolutionUnit, dtShort, []uint32{resPerInch}}, + } + if pr != prNone { + ifd = append(ifd, ifdEntry{tPredictor, dtShort, []uint32{pr}}) + } + if len(colorMap) != 0 { + ifd = append(ifd, ifdEntry{tColorMap, dtShort, colorMap}) + } + if extraSamples > 0 { + ifd = append(ifd, ifdEntry{tExtraSamples, dtShort, []uint32{extraSamples}}) + } + + return writeIFD(w, imageLen+8, ifd) +} diff --git a/api/admin.go b/api/admin.go index 568d8f6e8..3ef8c12a8 100644 --- a/api/admin.go +++ b/api/admin.go @@ -82,18 +82,11 @@ func saveConfig(c *Context, w http.ResponseWriter, r *http.Request) { return } - if len(cfg.ServiceSettings.ListenAddress) == 0 { - c.SetInvalidParam("saveConfig", "config") - return - } - - if cfg.TeamSettings.MaxUsersPerTeam == 0 { - c.SetInvalidParam("saveConfig", "config") + if err := cfg.IsValid(); err != nil { + c.Err = err return } - // TODO run some cleanup validators - utils.SaveConfig(utils.CfgFileName, cfg) utils.LoadConfig(utils.CfgFileName) json := utils.Cfg.ToJson() diff --git a/api/command.go b/api/command.go index 0d2f7597b..427922344 100644 --- a/api/command.go +++ b/api/command.go @@ -195,7 +195,7 @@ func joinCommand(c *Context, command *model.Command) bool { return false } - command.GotoLocation = "/channels/" + v.Name + command.GotoLocation = c.GetTeamURL() + "/channels/" + v.Name command.Response = model.RESP_EXECUTED return true } diff --git a/api/command_test.go b/api/command_test.go index fe52dd41b..360c4da58 100644 --- a/api/command_test.go +++ b/api/command_test.go @@ -4,7 +4,9 @@ package api import ( + "strings" "testing" + "time" "github.com/mattermost/platform/model" "github.com/mattermost/platform/store" @@ -126,12 +128,12 @@ func TestJoinCommands(t *testing.T) { } rs5 := Client.Must(Client.Command("", "/join "+channel2.Name, false)).Data.(*model.Command) - if rs5.GotoLocation != "/channels/"+channel2.Name { + if !strings.HasSuffix(rs5.GotoLocation, "/"+team.Name+"/channels/"+channel2.Name) { t.Fatal("failed to join channel") } rs6 := Client.Must(Client.Command("", "/join "+channel3.Name, false)).Data.(*model.Command) - if rs6.GotoLocation == "/channels/"+channel3.Name { + if strings.HasSuffix(rs6.GotoLocation, "/"+team.Name+"/channels/"+channel3.Name) { t.Fatal("should not have joined direct message channel") } @@ -175,6 +177,8 @@ func TestEchoCommand(t *testing.T) { t.Fatal("Echo command failed to execute") } + time.Sleep(100 * time.Millisecond) + p1 := Client.Must(Client.GetPosts(channel1.Id, 0, 2, "")).Data.(*model.PostList) if len(p1.Order) != 1 { t.Fatal("Echo command failed to send") diff --git a/api/file.go b/api/file.go index 5ed422811..5dc1db650 100644 --- a/api/file.go +++ b/api/file.go @@ -5,16 +5,15 @@ package api import ( "bytes" - "code.google.com/p/graphics-go/graphics" l4g "code.google.com/p/log4go" "fmt" + "github.com/disintegration/imaging" "github.com/goamz/goamz/aws" "github.com/goamz/goamz/s3" "github.com/gorilla/mux" "github.com/mattermost/platform/model" "github.com/mattermost/platform/utils" "github.com/mssola/user_agent" - "github.com/nfnt/resize" "github.com/rwcarlsen/goexif/exif" _ "golang.org/x/image/bmp" "image" @@ -24,7 +23,6 @@ import ( "image/jpeg" "io" "io/ioutil" - "math" "mime" "net/http" "net/url" @@ -163,7 +161,7 @@ func fireAndForgetHandleImages(filenames []string, fileData [][]byte, teamId, ch name := filename[:strings.LastIndex(filename, ".")] go func() { // Decode image bytes into Image object - img, _, err := image.Decode(bytes.NewReader(fileData[i])) + img, imgType, err := image.Decode(bytes.NewReader(fileData[i])) if err != nil { l4g.Error("Unable to decode image channelId=%v userId=%v filename=%v err=%v", channelId, userId, filename, err) return @@ -175,47 +173,30 @@ func fireAndForgetHandleImages(filenames []string, fileData [][]byte, teamId, ch // Get the image's orientation and ignore any errors since not all images will have orientation data orientation, _ := getImageOrientation(fileData[i]) - // Create a temporary image that will be manipulated and then used to make the thumbnail and preview image - var temp *image.RGBA - switch orientation { - case Upright, UprightMirrored, UpsideDown, UpsideDownMirrored: - temp = image.NewRGBA(img.Bounds()) - case RotatedCCW, RotatedCCWMirrored, RotatedCW, RotatedCWMirrored: - bounds := img.Bounds() - temp = image.NewRGBA(image.Rect(bounds.Min.Y, bounds.Min.X, bounds.Max.Y, bounds.Max.X)) - - width, height = height, width + if imgType == "png" { + dst := image.NewRGBA(img.Bounds()) + draw.Draw(dst, dst.Bounds(), image.NewUniform(color.White), image.Point{}, draw.Src) + draw.Draw(dst, dst.Bounds(), img, img.Bounds().Min, draw.Over) + img = dst } - // Draw a white background since JPEGs lack transparency - draw.Draw(temp, temp.Bounds(), image.NewUniform(color.White), image.Point{}, draw.Src) - - // Copy the original image onto the temporary one while rotating it as necessary switch orientation { - case UpsideDown, UpsideDownMirrored: - // rotate 180 degrees - err := graphics.Rotate(temp, img, &graphics.RotateOptions{Angle: math.Pi}) - if err != nil { - l4g.Error("Unable to rotate image") - } - case RotatedCW, RotatedCWMirrored: - // rotate 90 degrees CCW - graphics.Rotate(temp, img, &graphics.RotateOptions{Angle: 3 * math.Pi / 2}) - if err != nil { - l4g.Error("Unable to rotate image") - } - case RotatedCCW, RotatedCCWMirrored: - // rotate 90 degrees CW - graphics.Rotate(temp, img, &graphics.RotateOptions{Angle: math.Pi / 2}) - if err != nil { - l4g.Error("Unable to rotate image") - } - case Upright, UprightMirrored: - draw.Draw(temp, temp.Bounds(), img, img.Bounds().Min, draw.Over) + case UprightMirrored: + img = imaging.FlipH(img) + case UpsideDown: + img = imaging.Rotate180(img) + case UpsideDownMirrored: + img = imaging.FlipV(img) + case RotatedCWMirrored: + img = imaging.Transpose(img) + case RotatedCCW: + img = imaging.Rotate270(img) + case RotatedCCWMirrored: + img = imaging.Transverse(img) + case RotatedCW: + img = imaging.Rotate90(img) } - img = temp - // Create thumbnail go func() { thumbWidth := float64(utils.Cfg.FileSettings.ThumbnailWidth) @@ -227,9 +208,9 @@ func fireAndForgetHandleImages(filenames []string, fileData [][]byte, teamId, ch if imgHeight < thumbHeight && imgWidth < thumbWidth { thumbnail = img } else if imgHeight/imgWidth < thumbHeight/thumbWidth { - thumbnail = resize.Resize(0, utils.Cfg.FileSettings.ThumbnailHeight, img, resize.Lanczos3) + thumbnail = imaging.Resize(img, 0, utils.Cfg.FileSettings.ThumbnailHeight, imaging.Lanczos) } else { - thumbnail = resize.Resize(utils.Cfg.FileSettings.ThumbnailWidth, 0, img, resize.Lanczos3) + thumbnail = imaging.Resize(img, utils.Cfg.FileSettings.ThumbnailWidth, 0, imaging.Lanczos) } buf := new(bytes.Buffer) @@ -249,7 +230,7 @@ func fireAndForgetHandleImages(filenames []string, fileData [][]byte, teamId, ch go func() { var preview image.Image if width > int(utils.Cfg.FileSettings.PreviewWidth) { - preview = resize.Resize(utils.Cfg.FileSettings.PreviewWidth, utils.Cfg.FileSettings.PreviewHeight, img, resize.Lanczos3) + preview = imaging.Resize(img, utils.Cfg.FileSettings.PreviewWidth, utils.Cfg.FileSettings.PreviewHeight, imaging.Lanczos) } else { preview = img } diff --git a/api/team.go b/api/team.go index cff34390a..8e5d634aa 100644 --- a/api/team.go +++ b/api/team.go @@ -75,7 +75,10 @@ func signupTeam(c *Context, w http.ResponseWriter, r *http.Request) { return } - m["follow_link"] = bodyPage.Props["Link"] + if !utils.Cfg.EmailSettings.RequireEmailVerification { + m["follow_link"] = bodyPage.Props["Link"] + } + w.Header().Set("Access-Control-Allow-Origin", " *") w.Write([]byte(model.MapToJson(m))) } diff --git a/api/user.go b/api/user.go index a5c3fca2b..ed3576a30 100644 --- a/api/user.go +++ b/api/user.go @@ -8,13 +8,13 @@ import ( l4g "code.google.com/p/log4go" b64 "encoding/base64" "fmt" + "github.com/disintegration/imaging" "github.com/golang/freetype" "github.com/gorilla/mux" "github.com/mattermost/platform/model" "github.com/mattermost/platform/store" "github.com/mattermost/platform/utils" "github.com/mssola/user_agent" - "github.com/nfnt/resize" "hash/fnv" "image" "image/color" @@ -467,10 +467,14 @@ func RevokeAllSession(c *Context, userId string) { for _, session := range sessions { c.LogAuditWithUserId(userId, "session_id="+session.Id) - sessionCache.Remove(session.Token) - if result := <-Srv.Store.Session().Remove(session.Id); result.Err != nil { - c.Err = result.Err - return + if session.IsOAuth { + RevokeAccessToken(session.Token) + } else { + sessionCache.Remove(session.Token) + if result := <-Srv.Store.Session().Remove(session.Id); result.Err != nil { + c.Err = result.Err + return + } } } } @@ -799,7 +803,7 @@ func uploadProfileImage(c *Context, w http.ResponseWriter, r *http.Request) { } // Scale profile image - img = resize.Resize(utils.Cfg.FileSettings.ProfileWidth, utils.Cfg.FileSettings.ProfileHeight, img, resize.Lanczos3) + img = imaging.Resize(img, utils.Cfg.FileSettings.ProfileWidth, utils.Cfg.FileSettings.ProfileHeight, imaging.Lanczos) buf := new(bytes.Buffer) err = png.Encode(buf, img) @@ -994,7 +998,7 @@ func updateRoles(c *Context, w http.ResponseWriter, r *http.Request) { } else { sessions := result.Data.([]*model.Session) for _, s := range sessions { - sessionCache.Remove(s.Id) + sessionCache.Remove(s.Token) } } @@ -1417,7 +1421,7 @@ func GetAuthorizationCode(c *Context, w http.ResponseWriter, r *http.Request, te func AuthorizeOAuthUser(service, code, state, redirectUri string) (io.ReadCloser, *model.Team, *model.AppError) { sso := utils.Cfg.GetSSOService(service) - if sso != nil && !sso.Enable { + if sso == nil || !sso.Enable { return nil, nil, model.NewAppError("AuthorizeOAuthUser", "Unsupported OAuth service provider", "service="+service) } @@ -1459,6 +1463,9 @@ func AuthorizeOAuthUser(service, code, state, redirectUri string) (io.ReadCloser return nil, nil, model.NewAppError("AuthorizeOAuthUser", "Token request failed", err.Error()) } else { ar = model.AccessResponseFromJson(resp.Body) + if ar == nil { + return nil, nil, model.NewAppError("AuthorizeOAuthUser", "Bad response from token request", "") + } } if strings.ToLower(ar.TokenType) != model.ACCESS_TOKEN_TYPE { diff --git a/config/config.json b/config/config.json index 60e197154..38acee85a 100644 --- a/config/config.json +++ b/config/config.json @@ -5,7 +5,7 @@ "SegmentDeveloperKey": "", "GoogleDeveloperKey": "", "EnableOAuthServiceProvider": false, - "EnableIncomingWebhooks": false, + "EnableIncomingWebhooks": true, "EnableTesting": false }, "TeamSettings": { diff --git a/doc/README.md b/doc/README.md index 6bf96492a..7713c40ab 100644 --- a/doc/README.md +++ b/doc/README.md @@ -23,3 +23,7 @@ - [Code Contribution Guidelines](developer/code-contribution.md) - [Developer Machine Setup](install/dev-setup.md) - [Mattermost Style Guide](developer/style-guide.md) + +## End User Help + +- [Mattermost Markdown Formatting](help/enduser/markdown.md) diff --git a/doc/install/dev-setup.md b/doc/install/dev-setup.md index b90b6a351..c63bde512 100644 --- a/doc/install/dev-setup.md +++ b/doc/install/dev-setup.md @@ -3,13 +3,14 @@ Developer Machine Setup ### Mac OS X ### -1. Download and set up Boot2Docker +1. Download and set up Docker Toolbox 1. Follow the instructions at http://docs.docker.com/installation/mac/ - 1. Use the Boot2Docker command-line utility - 2. If you do command-line setup use: `boot2docker init eval “$(boot2docker shellinit)”` - 2. Get your Docker IP address with `boot2docker ip` + 2. Start a new docker host + `docker-machine create -d virtualbox dev` + 2. Get the IP address of your docker host + `docker-machine ip dev` 3. Add a line to your /etc/hosts that goes `<Docker IP> dockerhost` - 4. Run `boot2docker shellinit` and copy the export statements to your ~/.bash_profile + 4. Run `docker-machine env dev` and copy the export statements to your ~/.bash_profile 2. Download Go (version 1.4.2) from http://golang.org/dl/ 3. Set up your Go workspace 1. `mkdir ~/go` diff --git a/doc/install/single-container-install.md b/doc/install/single-container-install.md index 772f3becf..fa5265773 100644 --- a/doc/install/single-container-install.md +++ b/doc/install/single-container-install.md @@ -4,11 +4,11 @@ The following install instructions are for single-container installs of Mattermo ### Mac OSX ### -1. Install Boot2Docker using instructions at: http://docs.docker.com/installation/mac/ - 1. Start Boot2Docker from the command line and run: `boot2docker init eval “$(boot2docker shellinit)”` -2. Get your Docker IP address with: `boot2docker ip` +1. Install Docker Toolbox using instructions at: http://docs.docker.com/installation/mac/ + 1. Start Docker Toolbox from the command line and run: `docker-machine create -d virtualbox dev”` +2. Get your Docker IP address with: `docker-machine ip dev` 3. Use `sudo nano /etc/hosts` to add `<Docker IP> dockerhost` to your /etc/hosts file -4. Run: `boot2docker shellinit` and copy the export statements to your ~/.bash\_profile by running `sudo nano ~/.bash_profile`. Then run: `source ~/.bash_profile` +4. Run: `docker-machine env dev` and copy the export statements to your ~/.bash\_profile by running `sudo nano ~/.bash_profile`. Then run: `source ~/.bash_profile` 5. Run: `docker run --name mattermost-dev -d --publish 8065:80 mattermost/platform` 6. When docker is done fetching the image, open http://dockerhost:8065/ in your browser. diff --git a/doc/integrations/webhook/incoming.md b/doc/integrations/webhook/incoming.md new file mode 100644 index 000000000..e391cba92 --- /dev/null +++ b/doc/integrations/webhook/incoming.md @@ -0,0 +1,62 @@ +# Incoming Webhooks + +With incoming webhooks practically any external source - once given a URL by you - can post a message to any channel you have access to. This is done through a HTTP POST request with a simple JSON payload. The payload can contain some text, and some simple options to allow the external source to customize the post. + +## Creating the Webhook URL + +To get the incoming webhook URL - where all the HTTP requests will be sent - follow these steps: + +1. Login to your Mattermost account. +2. Open the menu by clicking near your profile picture in the top-left and open Account Settings. +3. Go to the Integrations tab and click the 'Edit' button next to 'Incoming Webhooks'. +4. Use the selector to choose a channel and click the 'Add' button to create the webhook. +5. Your webhook URL will be displayed below in the 'Existing incoming webhooks' section. + + +## Posting a Message + +You can send the message by including a JSON string as the `payload` parameter in a HTTP POST request. +``` +payload={"text": "Hello, this is some text."} +``` + +In addition, if `Content-Type` is specified as `application/json` in the headers of the HTTP request then the body of the request can be direct JSON. +``` +{"text": "Hello, this is some text."} +``` + +It is also possible to post richly formatted messages using [Markdown](../../help/enduser/markdown.md). +``` +payload={"text": "# A Header\nThe _text_ below **the** header."} +``` + +Just like regular posts, the text will be limited to 4000 characters at maximum. + +## Adding Links + +In addition to including links in the standard Markdown format, links can also be specified by enclosing the URL in `<>` brackets +``` +payload={"text": "<http://www.mattermost.com/>"} +``` + +They can also include a `|` character to specify some clickable text. +``` +payload={"text": "Click <http://www.mattermost.com/|here> for a link."} +``` + +## Channel Override + +You can use a single webhook URL to post messages to different channels by overriding the channel. You can do this by adding the channel name - as it is seen in the channel URL - to the request payload. +``` +payload={"channel": "off-topic", "text": "Hello, this is some text."} +``` + +## Finishing up + +Combining everything above, here is an example message made using a curl command: + +``` +curl -i -X POST 'payload={"channel": "off-topic", "text": "Hello, this is some text."}' http://yourmattermost.com/hooks/xxxxxxxxxxxxxxxxxxxxxxxxxx +``` + +A post with that text will be made to the Off-Topic channel. diff --git a/docker/dev/config_docker.json b/docker/dev/config_docker.json index 806071376..733267f74 100644 --- a/docker/dev/config_docker.json +++ b/docker/dev/config_docker.json @@ -5,7 +5,7 @@ "SegmentDeveloperKey": "", "GoogleDeveloperKey": "", "EnableOAuthServiceProvider": false, - "EnableIncomingWebhooks": false, + "EnableIncomingWebhooks": true, "EnableTesting": false }, "TeamSettings": { @@ -86,4 +86,4 @@ "TokenEndpoint": "", "UserApiEndpoint": "" } -}
\ No newline at end of file +} diff --git a/docker/local/config_docker.json b/docker/local/config_docker.json index 806071376..733267f74 100644 --- a/docker/local/config_docker.json +++ b/docker/local/config_docker.json @@ -5,7 +5,7 @@ "SegmentDeveloperKey": "", "GoogleDeveloperKey": "", "EnableOAuthServiceProvider": false, - "EnableIncomingWebhooks": false, + "EnableIncomingWebhooks": true, "EnableTesting": false }, "TeamSettings": { @@ -86,4 +86,4 @@ "TokenEndpoint": "", "UserApiEndpoint": "" } -}
\ No newline at end of file +} diff --git a/model/config.go b/model/config.go index 31c2b16dc..5d822e263 100644 --- a/model/config.go +++ b/model/config.go @@ -16,6 +16,9 @@ const ( IMAGE_DRIVER_LOCAL = "local" IMAGE_DRIVER_S3 = "amazons3" + DATABASE_DRIVER_MYSQL = "mysql" + DATABASE_DRIVER_POSTGRES = "postgres" + SERVICE_GITLAB = "gitlab" ) @@ -63,12 +66,12 @@ type FileSettings struct { Directory string EnablePublicLink bool PublicLinkSalt string - ThumbnailWidth uint - ThumbnailHeight uint - PreviewWidth uint - PreviewHeight uint - ProfileWidth uint - ProfileHeight uint + ThumbnailWidth int + ThumbnailHeight int + PreviewWidth int + PreviewHeight int + ProfileWidth int + ProfileHeight int InitialFont string AmazonS3AccessKeyId string AmazonS3SecretAccessKey string @@ -156,3 +159,92 @@ func ConfigFromJson(data io.Reader) *Config { return nil } } + +func (o *Config) IsValid() *AppError { + + if o.ServiceSettings.MaximumLoginAttempts <= 0 { + return NewAppError("Config.IsValid", "Invalid maximum login attempts for service settings. Must be a positive number.", "") + } + + if len(o.ServiceSettings.ListenAddress) == 0 { + return NewAppError("Config.IsValid", "Invalid listen address for service settings Must be set.", "") + } + + if o.TeamSettings.MaxUsersPerTeam <= 0 { + return NewAppError("Config.IsValid", "Invalid maximum users per team for team settings. Must be a positive number.", "") + } + + if len(o.SqlSettings.AtRestEncryptKey) != 32 { + return NewAppError("Config.IsValid", "Invalid at rest encrypt key for SQL settings. Must be 32 chars.", "") + } + + if !(o.SqlSettings.DriverName == DATABASE_DRIVER_MYSQL || o.SqlSettings.DriverName == DATABASE_DRIVER_POSTGRES) { + return NewAppError("Config.IsValid", "Invalid driver name for SQL settings. Must be 'mysql' or 'postgres'", "") + } + + if o.SqlSettings.MaxIdleConns <= 0 { + return NewAppError("Config.IsValid", "Invalid maximum idle connection for SQL settings. Must be a positive number.", "") + } + + if len(o.SqlSettings.DataSource) == 0 { + return NewAppError("Config.IsValid", "Invalid data source for SQL settings. Must be set.", "") + } + + if o.SqlSettings.MaxOpenConns <= 0 { + return NewAppError("Config.IsValid", "Invalid maximum open connection for SQL settings. Must be a positive number.", "") + } + + if !(o.FileSettings.DriverName == IMAGE_DRIVER_LOCAL || o.FileSettings.DriverName == IMAGE_DRIVER_S3) { + return NewAppError("Config.IsValid", "Invalid driver name for file settings. Must be 'local' or 'amazons3'", "") + } + + if o.FileSettings.PreviewHeight < 0 { + return NewAppError("Config.IsValid", "Invalid preview height for file settings. Must be a zero or positive number.", "") + } + + if o.FileSettings.PreviewWidth <= 0 { + return NewAppError("Config.IsValid", "Invalid preview width for file settings. Must be a positive number.", "") + } + + if o.FileSettings.ProfileHeight <= 0 { + return NewAppError("Config.IsValid", "Invalid profile height for file settings. Must be a positive number.", "") + } + + if o.FileSettings.ProfileWidth <= 0 { + return NewAppError("Config.IsValid", "Invalid profile width for file settings. Must be a positive number.", "") + } + + if o.FileSettings.ThumbnailHeight <= 0 { + return NewAppError("Config.IsValid", "Invalid thumbnail height for file settings. Must be a positive number.", "") + } + + if o.FileSettings.ThumbnailHeight <= 0 { + return NewAppError("Config.IsValid", "Invalid thumbnail width for file settings. Must be a positive number.", "") + } + + if len(o.FileSettings.PublicLinkSalt) != 32 { + return NewAppError("Config.IsValid", "Invalid public link salt for file settings. Must be 32 chars.", "") + } + + if !(o.EmailSettings.ConnectionSecurity == CONN_SECURITY_NONE || o.EmailSettings.ConnectionSecurity == CONN_SECURITY_TLS || o.EmailSettings.ConnectionSecurity == CONN_SECURITY_STARTTLS) { + return NewAppError("Config.IsValid", "Invalid connection security for email settings. Must be '', 'TLS', or 'STARTTLS'", "") + } + + if len(o.EmailSettings.InviteSalt) != 32 { + return NewAppError("Config.IsValid", "Invalid invite salt for email settings. Must be 32 chars.", "") + } + + if len(o.EmailSettings.PasswordResetSalt) != 32 { + return NewAppError("Config.IsValid", "Invalid password reset salt for email settings. Must be 32 chars.", "") + } + + if o.RateLimitSettings.MemoryStoreSize <= 0 { + return NewAppError("Config.IsValid", "Invalid memory store size for rate limit settings. Must be a positive number", "") + } + + if o.RateLimitSettings.PerSec <= 0 { + return NewAppError("Config.IsValid", "Invalid per sec for rate limit settings. Must be a positive number", "") + } + + return nil +} diff --git a/store/sql_channel_store.go b/store/sql_channel_store.go index 877246fc3..cb686090e 100644 --- a/store/sql_channel_store.go +++ b/store/sql_channel_store.go @@ -583,7 +583,7 @@ func (s SqlChannelStore) UpdateLastViewedAt(channelId string, userId string) Sto var query string - if utils.Cfg.SqlSettings.DriverName == "postgres" { + if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES { query = `UPDATE ChannelMembers SET @@ -597,7 +597,7 @@ func (s SqlChannelStore) UpdateLastViewedAt(channelId string, userId string) Sto Channels.Id = ChannelMembers.ChannelId AND UserId = :UserId AND ChannelId = :ChannelId` - } else if utils.Cfg.SqlSettings.DriverName == "mysql" { + } else if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_MYSQL { query = `UPDATE ChannelMembers, Channels SET diff --git a/store/sql_post_store.go b/store/sql_post_store.go index 21e8e9d00..668f45fbb 100644 --- a/store/sql_post_store.go +++ b/store/sql_post_store.go @@ -418,7 +418,7 @@ func (s SqlPostStore) Search(teamId string, userId string, terms string, isHasht var posts []*model.Post - if utils.Cfg.SqlSettings.DriverName == "postgres" { + if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES { // Parse text for wildcards if wildcard, err := regexp.Compile("\\*($| )"); err == nil { @@ -451,7 +451,7 @@ func (s SqlPostStore) Search(teamId string, userId string, terms string, isHasht result.Err = model.NewAppError("SqlPostStore.Search", "We encounted an error while searching for posts", "teamId="+teamId+", err="+err.Error()) } - } else if utils.Cfg.SqlSettings.DriverName == "mysql" { + } else if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_MYSQL { searchQuery := fmt.Sprintf(`SELECT * FROM diff --git a/store/sql_post_store_test.go b/store/sql_post_store_test.go index bc1cb2c2c..257054033 100644 --- a/store/sql_post_store_test.go +++ b/store/sql_post_store_test.go @@ -530,13 +530,6 @@ func TestPostStoreSearch(t *testing.T) { t.Fatal("returned wrong serach result") } - // if utils.Cfg.SqlSettings.DriverName == "mysql" { - // r2 := (<-store.Post().Search(teamId, userId, "new york", false)).Data.(*model.PostList) - // if len(r2.Order) >= 1 && r2.Order[0] != o2.Id { - // t.Fatal("returned wrong serach result") - // } - // } - r3 := (<-store.Post().Search(teamId, userId, "new", false)).Data.(*model.PostList) if len(r3.Order) != 2 && r3.Order[0] != o1.Id { t.Fatal("returned wrong serach result") diff --git a/store/sql_store.go b/store/sql_store.go index 6dcf2e8cd..3ef9cfbc4 100644 --- a/store/sql_store.go +++ b/store/sql_store.go @@ -160,9 +160,9 @@ func setupConnection(con_type string, driver string, dataSource string, maxIdle if driver == "sqlite3" { dbmap = &gorp.DbMap{Db: db, TypeConverter: mattermConverter{}, Dialect: gorp.SqliteDialect{}} - } else if driver == "mysql" { + } else if driver == model.DATABASE_DRIVER_MYSQL { dbmap = &gorp.DbMap{Db: db, TypeConverter: mattermConverter{}, Dialect: gorp.MySQLDialect{Engine: "InnoDB", Encoding: "UTF8MB4"}} - } else if driver == "postgres" { + } else if driver == model.DATABASE_DRIVER_POSTGRES { dbmap = &gorp.DbMap{Db: db, TypeConverter: mattermConverter{}, Dialect: gorp.PostgresDialect{}} } else { l4g.Critical("Failed to create dialect specific driver") @@ -183,7 +183,7 @@ func (ss SqlStore) GetCurrentSchemaVersion() string { } func (ss SqlStore) DoesTableExist(tableName string) bool { - if utils.Cfg.SqlSettings.DriverName == "postgres" { + if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES { count, err := ss.GetMaster().SelectInt( `SELECT count(relname) FROM pg_class WHERE relname=$1`, strings.ToLower(tableName), @@ -197,7 +197,7 @@ func (ss SqlStore) DoesTableExist(tableName string) bool { return count > 0 - } else if utils.Cfg.SqlSettings.DriverName == "mysql" { + } else if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_MYSQL { count, err := ss.GetMaster().SelectInt( `SELECT @@ -228,7 +228,7 @@ func (ss SqlStore) DoesTableExist(tableName string) bool { } func (ss SqlStore) DoesColumnExist(tableName string, columnName string) bool { - if utils.Cfg.SqlSettings.DriverName == "postgres" { + if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES { count, err := ss.GetMaster().SelectInt( `SELECT COUNT(0) FROM pg_attribute @@ -251,7 +251,7 @@ func (ss SqlStore) DoesColumnExist(tableName string, columnName string) bool { return count > 0 - } else if utils.Cfg.SqlSettings.DriverName == "mysql" { + } else if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_MYSQL { count, err := ss.GetMaster().SelectInt( `SELECT @@ -288,7 +288,7 @@ func (ss SqlStore) CreateColumnIfNotExists(tableName string, columnName string, return false } - if utils.Cfg.SqlSettings.DriverName == "postgres" { + if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES { _, err := ss.GetMaster().Exec("ALTER TABLE " + tableName + " ADD " + columnName + " " + postgresColType + " DEFAULT '" + defaultValue + "'") if err != nil { l4g.Critical("Failed to create column %v", err) @@ -298,7 +298,7 @@ func (ss SqlStore) CreateColumnIfNotExists(tableName string, columnName string, return true - } else if utils.Cfg.SqlSettings.DriverName == "mysql" { + } else if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_MYSQL { _, err := ss.GetMaster().Exec("ALTER TABLE " + tableName + " ADD " + columnName + " " + mySqlColType + " DEFAULT '" + defaultValue + "'") if err != nil { l4g.Critical("Failed to create column %v", err) @@ -334,7 +334,7 @@ func (ss SqlStore) RemoveColumnIfExists(tableName string, columnName string) boo // func (ss SqlStore) RenameColumnIfExists(tableName string, oldColumnName string, newColumnName string, colType string) bool { // // XXX TODO FIXME this should be removed after 0.6.0 -// if utils.Cfg.SqlSettings.DriverName == "postgres" { +// if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES { // return false // } @@ -363,7 +363,7 @@ func (ss SqlStore) CreateFullTextIndexIfNotExists(indexName string, tableName st func (ss SqlStore) createIndexIfNotExists(indexName string, tableName string, columnName string, fullText bool) { - if utils.Cfg.SqlSettings.DriverName == "postgres" { + if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES { _, err := ss.GetMaster().SelectStr("SELECT $1::regclass", indexName) // It should fail if the index does not exist if err == nil { @@ -383,7 +383,7 @@ func (ss SqlStore) createIndexIfNotExists(indexName string, tableName string, co time.Sleep(time.Second) panic("Failed to create index " + err.Error()) } - } else if utils.Cfg.SqlSettings.DriverName == "mysql" { + } else if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_MYSQL { count, err := ss.GetMaster().SelectInt("SELECT COUNT(0) AS index_exists FROM information_schema.statistics WHERE TABLE_SCHEMA = DATABASE() and table_name = ? AND index_name = ?", tableName, indexName) if err != nil { diff --git a/utils/config.go b/utils/config.go index 5d786699b..3218211e3 100644 --- a/utils/config.go +++ b/utils/config.go @@ -150,7 +150,12 @@ func LoadConfig(fileName string) { CfgFileName = fileName } + if err := config.IsValid(); err != nil { + panic("Error validating config file=" + fileName + ", err=" + err.Message) + } + configureLog(&config.LogSettings) + TestConnection(&config) Cfg = &config SanitizeOptions = getSanitizeOptions(Cfg) diff --git a/utils/mail.go b/utils/mail.go index dd975155d..f6fe1ce00 100644 --- a/utils/mail.go +++ b/utils/mail.go @@ -65,13 +65,34 @@ func newSMTPClient(conn net.Conn, config *model.Config) (*smtp.Client, *model.Ap return c, nil } +func TestConnection(config *model.Config) { + if !config.EmailSettings.SendEmailNotifications { + return + } + + conn, err1 := connectToSMTPServer(config) + if err1 != nil { + l4g.Error("SMTP server settings do not appear to be configured properly err=%v details=%v", err1.Message, err1.DetailedError) + return + } + defer conn.Close() + + c, err2 := newSMTPClient(conn, config) + if err2 != nil { + l4g.Error("SMTP connection settings do not appear to be configured properly err=%v details=%v", err2.Message, err2.DetailedError) + return + } + defer c.Quit() + defer c.Close() +} + func SendMail(to, subject, body string) *model.AppError { return SendMailUsingConfig(to, subject, body, Cfg) } func SendMailUsingConfig(to, subject, body string, config *model.Config) *model.AppError { - if !config.EmailSettings.SendEmailNotifications { + if !config.EmailSettings.SendEmailNotifications || len(config.EmailSettings.SMTPServer) == 0 { return nil } diff --git a/web/react/components/admin_console/email_settings.jsx b/web/react/components/admin_console/email_settings.jsx index 3b5ad2a1a..762a4ab26 100644 --- a/web/react/components/admin_console/email_settings.jsx +++ b/web/react/components/admin_console/email_settings.jsx @@ -288,7 +288,7 @@ export default class EmailSettings extends React.Component { className='control-label col-sm-4' htmlFor='feedbackName' > - {'Feedback Name:'} + {'Notification Display Name:'} </label> <div className='col-sm-8'> <input @@ -310,7 +310,7 @@ export default class EmailSettings extends React.Component { className='control-label col-sm-4' htmlFor='feedbackEmail' > - {'Feedback Email:'} + {'Notification Email Address:'} </label> <div className='col-sm-8'> <input diff --git a/web/react/components/admin_console/gitlab_settings.jsx b/web/react/components/admin_console/gitlab_settings.jsx index eaea15b8f..759892ad3 100644 --- a/web/react/components/admin_console/gitlab_settings.jsx +++ b/web/react/components/admin_console/gitlab_settings.jsx @@ -40,7 +40,6 @@ export default class GitLabSettings extends React.Component { config.GitLabSettings.Enable = React.findDOMNode(this.refs.Enable).checked; config.GitLabSettings.Secret = React.findDOMNode(this.refs.Secret).value.trim(); config.GitLabSettings.Id = React.findDOMNode(this.refs.Id).value.trim(); - config.GitLabSettings.Scope = React.findDOMNode(this.refs.Scope).value.trim(); config.GitLabSettings.AuthEndpoint = React.findDOMNode(this.refs.AuthEndpoint).value.trim(); config.GitLabSettings.TokenEndpoint = React.findDOMNode(this.refs.TokenEndpoint).value.trim(); config.GitLabSettings.UserApiEndpoint = React.findDOMNode(this.refs.UserApiEndpoint).value.trim(); @@ -121,28 +120,6 @@ export default class GitLabSettings extends React.Component { <div className='form-group'> <label className='control-label col-sm-4' - htmlFor='Secret' - > - {'Secret:'} - </label> - <div className='col-sm-8'> - <input - type='text' - className='form-control' - id='Secret' - ref='Secret' - placeholder='Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"' - defaultValue={this.props.config.GitLabSettings.Secret} - onChange={this.handleChange} - disabled={!this.state.Enable} - /> - <p className='help-text'>{'Obtain this value via the instructions above for logging into GitLab.'}</p> - </div> - </div> - - <div className='form-group'> - <label - className='control-label col-sm-4' htmlFor='Id' > {'Id:'} @@ -165,22 +142,22 @@ export default class GitLabSettings extends React.Component { <div className='form-group'> <label className='control-label col-sm-4' - htmlFor='Scope' + htmlFor='Secret' > - {'Scope:'} + {'Secret:'} </label> <div className='col-sm-8'> <input type='text' className='form-control' - id='Scope' - ref='Scope' - placeholder='Not currently used by GitLab. Please leave blank' - defaultValue={this.props.config.GitLabSettings.Scope} + id='Secret' + ref='Secret' + placeholder='Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"' + defaultValue={this.props.config.GitLabSettings.Secret} onChange={this.handleChange} disabled={!this.state.Enable} /> - <p className='help-text'>{'This field is not yet used by GitLab OAuth. Other OAuth providers may use this field to specify the scope of account data from OAuth provider that is sent to Mattermost.'}</p> + <p className='help-text'>{'Obtain this value via the instructions above for logging into GitLab.'}</p> </div> </div> @@ -202,7 +179,7 @@ export default class GitLabSettings extends React.Component { onChange={this.handleChange} disabled={!this.state.Enable} /> - <p className='help-text'>{'Enter <your-gitlab-url>/oauth/authorize (example http://localhost:3000/oauth/authorize).'}</p> + <p className='help-text'>{'Enter <your-gitlab-url>/oauth/authorize (example http://localhost:3000/oauth/authorize). Make sure you use HTTP or HTTPS in your URLs as appropriate.'}</p> </div> </div> @@ -224,7 +201,7 @@ export default class GitLabSettings extends React.Component { onChange={this.handleChange} disabled={!this.state.Enable} /> - <p className='help-text'>{'Enter <your-gitlab-url>/oauth/token.'}</p> + <p className='help-text'>{'Enter <your-gitlab-url>/oauth/token. Make sure you use HTTP or HTTPS in your URLs as appropriate.'}</p> </div> </div> @@ -246,7 +223,7 @@ export default class GitLabSettings extends React.Component { onChange={this.handleChange} disabled={!this.state.Enable} /> - <p className='help-text'>{'Enter <your-gitlab-url>/api/v3/user.'}</p> + <p className='help-text'>{'Enter <your-gitlab-url>/api/v3/user. Make sure you use HTTP or HTTPS in your URLs as appropriate.'}</p> </div> </div> @@ -272,6 +249,30 @@ export default class GitLabSettings extends React.Component { } } + +//config.GitLabSettings.Scope = React.findDOMNode(this.refs.Scope).value.trim(); +// <div className='form-group'> +// <label +// className='control-label col-sm-4' +// htmlFor='Scope' +// > +// {'Scope:'} +// </label> +// <div className='col-sm-8'> +// <input +// type='text' +// className='form-control' +// id='Scope' +// ref='Scope' +// placeholder='Not currently used by GitLab. Please leave blank' +// defaultValue={this.props.config.GitLabSettings.Scope} +// onChange={this.handleChange} +// disabled={!this.state.Allow} +// /> +// <p className='help-text'>{'This field is not yet used by GitLab OAuth. Other OAuth providers may use this field to specify the scope of account data from OAuth provider that is sent to Mattermost.'}</p> +// </div> +// </div> + GitLabSettings.propTypes = { config: React.PropTypes.object }; diff --git a/web/react/components/command_list.jsx b/web/react/components/command_list.jsx index 63bd57c2a..fea7085b7 100644 --- a/web/react/components/command_list.jsx +++ b/web/react/components/command_list.jsx @@ -96,4 +96,4 @@ CommandList.defaultProps = { CommandList.propTypes = { addCommand: React.PropTypes.func, channelId: React.PropTypes.string -};
\ No newline at end of file +}; diff --git a/web/react/components/more_direct_channels.jsx b/web/react/components/more_direct_channels.jsx index b7bce9b34..54d77c358 100644 --- a/web/react/components/more_direct_channels.jsx +++ b/web/react/components/more_direct_channels.jsx @@ -43,7 +43,7 @@ export default class MoreDirectChannels extends React.Component { handleClick = function clickHandler(e) { e.preventDefault(); - utils.switchChannel(channel, channel.teammate_username); + utils.switchChannel(channel); $(React.findDOMNode(self.refs.modal)).modal('hide'); }; } else { diff --git a/web/react/components/navbar_dropdown.jsx b/web/react/components/navbar_dropdown.jsx index 4c01d2c43..57a78a0d4 100644 --- a/web/react/components/navbar_dropdown.jsx +++ b/web/react/components/navbar_dropdown.jsx @@ -62,7 +62,7 @@ export default class NavbarDropdown extends React.Component { if (currentUser != null) { isAdmin = Utils.isAdmin(currentUser.roles); - isSystemAdmin = Utils.isInRole(currentUser.roles, 'system_admin'); + isSystemAdmin = Utils.isSystemAdmin(currentUser.roles); inviteLink = ( <li> diff --git a/web/react/components/popover_list_members.jsx b/web/react/components/popover_list_members.jsx index a2ca8b00f..95a88c3d6 100644 --- a/web/react/components/popover_list_members.jsx +++ b/web/react/components/popover_list_members.jsx @@ -1,6 +1,8 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. +var UserStore = require('../stores/user_store.jsx'); + export default class PopoverListMembers extends React.Component { componentDidMount() { const originalLeave = $.fn.popover.Constructor.prototype.leave; @@ -32,22 +34,28 @@ export default class PopoverListMembers extends React.Component { } render() { let popoverHtml = ''; + let count = 0; + let countText = '-'; const members = this.props.members; - let count; - if (members.length > 20) { - count = '20+'; - } else { - count = members.length || '-'; - } + const teamMembers = UserStore.getProfilesUsernameMap(); - if (members) { + if (members && teamMembers) { members.sort(function compareByLocal(a, b) { return a.username.localeCompare(b.username); }); members.forEach(function addMemberElement(m) { - popoverHtml += `<div class='text--nowrap'>${m.username}</div>`; + if (teamMembers[m.username] && teamMembers[m.username].delete_at <= 0) { + popoverHtml += `<div class='text--nowrap'>${m.username}</div>`; + count++; + } }); + + if (count > 20) { + countText = '20+'; + } else if (count > 0) { + countText = count.toString(); + } } return ( @@ -63,7 +71,7 @@ export default class PopoverListMembers extends React.Component { data-toggle='tooltip' title='View Channel Members' > - {count} + {countText} <span className='fa fa-user' aria-hidden='true' diff --git a/web/react/components/post_body.jsx b/web/react/components/post_body.jsx index 6e98e4aba..48b268700 100644 --- a/web/react/components/post_body.jsx +++ b/web/react/components/post_body.jsx @@ -12,7 +12,10 @@ export default class PostBody extends React.Component { constructor(props) { super(props); + this.receivedYoutubeData = false; + this.parseEmojis = this.parseEmojis.bind(this); + this.createYoutubeEmbed = this.createYoutubeEmbed.bind(this); const linkData = Utils.extractLinks(this.props.post.message); this.state = {links: linkData.links, message: linkData.text}; @@ -50,6 +53,123 @@ export default class PostBody extends React.Component { this.setState({links: linkData.links, message: linkData.text}); } + handleYoutubeTime(link) { + const timeRegex = /[\\?&]t=([0-9hms]+)/; + + const time = link.trim().match(timeRegex); + if (!time || !time[1]) { + return ''; + } + + const hours = time[1].match(/([0-9]+)h/); + const minutes = time[1].match(/([0-9]+)m/); + const seconds = time[1].match(/([0-9]+)s/); + + let ticks = 0; + + if (hours && hours[1]) { + ticks += parseInt(hours[1], 10) * 3600; + } + + if (minutes && minutes[1]) { + ticks += parseInt(minutes[1], 10) * 60; + } + + if (seconds && seconds[1]) { + ticks += parseInt(seconds[1], 10); + } + + return '&start=' + ticks.toString(); + } + + createYoutubeEmbed(link) { + const ytRegex = /.*(?:youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|watch\?(?:[a-zA-Z-_]+=[a-zA-Z0-9-_]+&)+v=)([^#\&\?]*).*/; + + const match = link.trim().match(ytRegex); + if (!match || match[1].length !== 11) { + return null; + } + + const youtubeId = match[1]; + const time = this.handleYoutubeTime(link); + + function onClick(e) { + var div = $(e.target).closest('.video-thumbnail__container')[0]; + var iframe = document.createElement('iframe'); + iframe.setAttribute('src', + 'https://www.youtube.com/embed/' + + div.id + + '?autoplay=1&autohide=1&border=0&wmode=opaque&fs=1&enablejsapi=1' + + time); + iframe.setAttribute('width', '480px'); + iframe.setAttribute('height', '360px'); + iframe.setAttribute('type', 'text/html'); + iframe.setAttribute('frameborder', '0'); + iframe.setAttribute('allowfullscreen', 'allowfullscreen'); + + div.parentNode.replaceChild(iframe, div); + } + + function success(data) { + if (!data.items.length || !data.items[0].snippet) { + return null; + } + var metadata = data.items[0].snippet; + this.receivedYoutubeData = true; + this.setState({youtubeUploader: metadata.channelTitle, youtubeTitle: metadata.title}); + } + + if (global.window.config.GoogleDeveloperKey && !this.receivedYoutubeData) { + $.ajax({ + async: true, + url: 'https://www.googleapis.com/youtube/v3/videos', + type: 'GET', + data: {part: 'snippet', id: youtubeId, key: global.window.config.GoogleDeveloperKey}, + success + }); + } + + let header = 'Youtube'; + if (this.state.youtubeTitle) { + header = header + ' - '; + } + + let uploader = this.state.youtubeUploader; + if (!uploader) { + uploader = 'unknown'; + } + + return ( + <div className='post-comment'> + <h4> + <span className='video-type'>{header}</span> + <span className='video-title'><a href={link}>{this.state.youtubeTitle}</a></span> + </h4> + <h4 className='video-uploader'>{uploader}</h4> + <div + className='video-div embed-responsive-item' + id={youtubeId} + onClick={onClick} + > + <div className='embed-responsive embed-responsive-4by3 video-div__placeholder'> + <div + id={youtubeId} + className='video-thumbnail__container' + > + <img + className='video-thumbnail' + src={'https://i.ytimg.com/vi/' + youtubeId + '/hqdefault.jpg'} + /> + <div className='block'> + <span className='play-button'><span/></span> + </div> + </div> + </div> + </div> + </div> + ); + } + render() { const post = this.props.post; const filenames = this.props.post.filenames; @@ -133,7 +253,7 @@ export default class PostBody extends React.Component { let embed; if (filenames.length === 0 && this.state.links) { - embed = Utils.getEmbed(this.state.links[0]); + embed = this.createYoutubeEmbed(this.state.links[0]); } let fileAttachmentHolder = ''; diff --git a/web/react/components/post_info.jsx b/web/react/components/post_info.jsx index 824e7ef39..82e746dc0 100644 --- a/web/react/components/post_info.jsx +++ b/web/react/components/post_info.jsx @@ -150,7 +150,7 @@ export default class PostInfo extends React.Component { var dropdown = this.createDropdown(); - let tooltip = <Tooltip>{utils.displayDate(post.create_at)} at ${utils.displayTime(post.create_at)}</Tooltip>; + let tooltip = <Tooltip id={post.id + 'tooltip'}>{utils.displayDate(post.create_at)} at ${utils.displayTime(post.create_at)}</Tooltip>; return ( <ul className='post-header post-info'> diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx index 3e1e075bb..a31967257 100644 --- a/web/react/components/post_list.jsx +++ b/web/react/components/post_list.jsx @@ -128,6 +128,15 @@ export default class PostList extends React.Component { this.userHasSeenNew = true; } this.isUserScroll = true; + + $('.top-visible-post').removeClass('top-visible-post'); + + $(React.findDOMNode(this.refs.postlistcontent)).children().each(function select() { + if ($(this).position().top + $(this).height() / 2 > 0) { + $(this).addClass('top-visible-post'); + return false; + } + }); }); $('.post-list__content div .post').removeClass('post--last'); @@ -665,7 +674,10 @@ export default class PostList extends React.Component { className={'post-list-holder-by-time ' + activeClass} > <div className='post-list__table'> - <div className='post-list__content'> + <div + ref='postlistcontent' + className='post-list__content' + > {moreMessages} {postCtls} </div> diff --git a/web/react/components/rename_channel_modal.jsx b/web/react/components/rename_channel_modal.jsx index 37958b649..9d514c741 100644 --- a/web/react/components/rename_channel_modal.jsx +++ b/web/react/components/rename_channel_modal.jsx @@ -78,7 +78,6 @@ export default class RenameChannelModal extends React.Component { $(React.findDOMNode(this.refs.modal)).modal('hide'); AsyncClient.getChannel(channel.id); - Utils.updateTabTitle(channel.display_name); Utils.updateAddressBar(channel.name); React.findDOMNode(this.refs.displayName).value = ''; diff --git a/web/react/components/search_results_item.jsx b/web/react/components/search_results_item.jsx index 32b521560..bdefdbee8 100644 --- a/web/react/components/search_results_item.jsx +++ b/web/react/components/search_results_item.jsx @@ -47,13 +47,8 @@ export default class SearchResultsItem extends React.Component { ); var postChannel = ChannelStore.get(this.props.post.channel_id); - var teammate = ''; - if (postChannel.type === 'D') { - teammate = utils.getDirectTeammate(this.props.post.channel_id).username; - } - - utils.switchChannel(postChannel, teammate); + utils.switchChannel(postChannel); } render() { diff --git a/web/react/components/setting_upload.jsx b/web/react/components/setting_upload.jsx index fad27b355..ccb26cc58 100644 --- a/web/react/components/setting_upload.jsx +++ b/web/react/components/setting_upload.jsx @@ -7,11 +7,11 @@ export default class SettingsUpload extends React.Component { this.doFileSelect = this.doFileSelect.bind(this); this.doSubmit = this.doSubmit.bind(this); - this.onFileSelect = this.onFileSelect.bind(this); this.state = { clientError: this.props.clientError, - serverError: this.props.serverError + serverError: this.props.serverError, + filename: '' }; } @@ -24,9 +24,14 @@ export default class SettingsUpload extends React.Component { doFileSelect(e) { e.preventDefault(); + var filename = $(e.target).val(); + if (filename.substring(3, 11) === 'fakepath') { + filename = filename.substring(12); + } this.setState({ clientError: '', - serverError: '' + serverError: '', + filename }); } @@ -40,28 +45,28 @@ export default class SettingsUpload extends React.Component { } } - onFileSelect(e) { - var filename = $(e.target).val(); - if (filename.substring(3, 11) === 'fakepath') { - filename = filename.substring(12); - } - $(e.target).closest('li').find('.file-status').addClass('hide'); - $(e.target).closest('li').find('.file-name').removeClass('hide').html(filename); - } - render() { - var clientError = null; + let clientError = null; if (this.state.clientError) { clientError = ( <div className='file-status'>{this.state.clientError}</div> ); } - var serverError = null; + let serverError = null; if (this.state.serverError) { serverError = ( <div className='file-status'>{this.state.serverError}</div> ); } + let fileNameText = null; + let submitButtonClass = 'btn btn-sm btn-primary disabled'; + if (this.state.filename) { + fileNameText = ( + <div className='file-status file-name'>{this.state.filename}</div> + ); + submitButtonClass = 'btn btn-sm btn-primary'; + } + return ( <ul className='section-max'> <li className='col-sm-12 section-title'>{this.props.title}</li> @@ -70,21 +75,21 @@ export default class SettingsUpload extends React.Component { <ul className='setting-list'> <li className='setting-list-item'> <span className='btn btn-sm btn-primary btn-file sel-btn'> - Select file + {'Select file'} <input ref='uploadinput' accept={this.props.fileTypesAccepted} type='file' - onChange={this.onFileSelect} + onChange={this.doFileSelect} /> </span> <a - className={'btn btn-sm btn-primary'} + className={submitButtonClass} onClick={this.doSubmit} > - Import + {'Import'} </a> - <div className='file-status file-name hide'></div> + {fileNameText} {serverError} {clientError} </li> @@ -102,4 +107,4 @@ SettingsUpload.propTypes = { clientError: React.PropTypes.string, serverError: React.PropTypes.string, helpText: React.PropTypes.object -}; +};
\ No newline at end of file diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx index 14664ed4d..6033f200f 100644 --- a/web/react/components/sidebar.jsx +++ b/web/react/components/sidebar.jsx @@ -268,14 +268,19 @@ export default class Sidebar extends React.Component { } } updateTitle() { - var channel = ChannelStore.getCurrent(); + const channel = ChannelStore.getCurrent(); if (channel) { + let currentSiteName = ''; + if (global.window.config.SiteName != null) { + currentSiteName = global.window.config.SiteName; + } + + let currentChannelName = channel.display_name; if (channel.type === 'D') { - var teammateUsername = Utils.getDirectTeammate(channel.id).username; - document.title = teammateUsername + ' ' + document.title.substring(document.title.lastIndexOf('-')); - } else { - document.title = channel.display_name + ' ' + document.title.substring(document.title.lastIndexOf('-')); + currentChannelName = Utils.getDirectTeammate(channel.id).username; } + + document.title = currentChannelName + ' - ' + this.props.teamDisplayName + ' ' + currentSiteName; } } onScroll() { diff --git a/web/react/components/sidebar_right.jsx b/web/react/components/sidebar_right.jsx index 913715154..708cd04cb 100644 --- a/web/react/components/sidebar_right.jsx +++ b/web/react/components/sidebar_right.jsx @@ -14,9 +14,10 @@ export default class SidebarRight extends React.Component { constructor(props) { super(props); + this.plScrolledToBottom = true; + this.onSelectedChange = this.onSelectedChange.bind(this); this.onSearchChange = this.onSearchChange.bind(this); - this.resize = this.resize.bind(this); this.state = getStateFromStores(); } @@ -28,6 +29,14 @@ export default class SidebarRight extends React.Component { PostStore.removeSearchChangeListener(this.onSearchChange); PostStore.removeSelectedPostChangeListener(this.onSelectedChange); } + componentDidUpdate() { + if (!this.plScrolledToBottom) { + $('.top-visible-post')[0].scrollIntoView(); + } else { + var postHolder = $('.post-list-holder-by-time').not('.inactive'); + postHolder.scrollTop(postHolder[0].scrollHeight); + } + } onSelectedChange(fromSearch) { var newState = getStateFromStores(fromSearch); newState.from_search = fromSearch; @@ -41,15 +50,15 @@ export default class SidebarRight extends React.Component { this.setState(newState); } } - resize() { - var postHolder = $('.post-list-holder-by-time'); - postHolder[0].scrollTop = postHolder[0].scrollHeight - 224; - } render() { + var postHolder = $('.post-list-holder-by-time').not('.inactive'); + const position = postHolder.scrollTop() + postHolder.height() + 14; + const bottom = postHolder[0].scrollHeight; + this.plScrolledToBottom = position >= bottom; + if (!(this.state.search_visible || this.state.post_right_visible)) { $('.inner__wrap').removeClass('move--left').removeClass('move--right'); $('.sidebar--right').removeClass('move--left'); - this.resize(); return ( <div></div> ); @@ -59,8 +68,8 @@ export default class SidebarRight extends React.Component { $('.sidebar--left').removeClass('move--right'); $('.sidebar--right').addClass('move--left'); $('.sidebar--right').prepend('<div class="sidebar__overlay"></div>'); - this.resize(); - setTimeout(function overlayTimer() { + + setTimeout(() => { $('.sidebar__overlay').fadeOut('200', function fadeOverlay() { $(this).remove(); }); diff --git a/web/react/components/sidebar_right_menu.jsx b/web/react/components/sidebar_right_menu.jsx index f1341d9d7..2df2c8ffd 100644 --- a/web/react/components/sidebar_right_menu.jsx +++ b/web/react/components/sidebar_right_menu.jsx @@ -26,11 +26,14 @@ export default class SidebarRightMenu extends React.Component { var inviteLink = ''; var teamSettingsLink = ''; var manageLink = ''; + var consoleLink = ''; var currentUser = UserStore.getCurrentUser(); var isAdmin = false; + var isSystemAdmin = false; if (currentUser != null) { isAdmin = utils.isAdmin(currentUser.roles); + isSystemAdmin = utils.isSystemAdmin(currentUser.roles); inviteLink = ( <li> @@ -77,6 +80,17 @@ export default class SidebarRightMenu extends React.Component { ); } + if (isSystemAdmin) { + consoleLink = ( + <li> + <a + href='/admin_console' + > + <i className='glyphicon glyphicon-wrench'></i>System Console</a> + </li> + ); + } + var siteName = ''; if (global.window.config.SiteName != null) { siteName = global.window.config.SiteName; @@ -107,6 +121,7 @@ export default class SidebarRightMenu extends React.Component { {inviteLink} {teamLink} {manageLink} + {consoleLink} <li> <a href='#' diff --git a/web/react/components/team_signup_url_page.jsx b/web/react/components/team_signup_url_page.jsx index 1b722d611..a3f89a217 100644 --- a/web/react/components/team_signup_url_page.jsx +++ b/web/react/components/team_signup_url_page.jsx @@ -35,8 +35,8 @@ export default class TeamSignupUrlPage extends React.Component { if (cleanedName !== name || !urlRegex.test(name)) { this.setState({nameError: "Use only lower case letters, numbers and dashes. Must start with a letter and can't end in a dash."}); return; - } else if (cleanedName.length <= 3 || cleanedName.length > 15) { - this.setState({nameError: 'Name must be 4 or more characters up to a maximum of 15'}); + } else if (cleanedName.length <= 2 || cleanedName.length > 15) { + this.setState({nameError: 'Name must be 3 or more characters up to a maximum of 15'}); return; } diff --git a/web/react/components/team_signup_with_sso.jsx b/web/react/components/team_signup_with_sso.jsx index a4972dd8d..14f281f7a 100644 --- a/web/react/components/team_signup_with_sso.jsx +++ b/web/react/components/team_signup_with_sso.jsx @@ -23,12 +23,14 @@ export default class SSOSignUpPage extends React.Component { team.display_name = this.state.name; - if (team.display_name.length <= 3) { + if (!team.display_name) { + state.nameError = 'Please enter a team name'; + this.setState(state); return; } - if (!team.display_name) { - state.nameError = 'Please enter a team name'; + if (team.display_name.length <= 2) { + state.nameError = 'Name must be 3 or more characters up to a maximum of 15'; this.setState(state); return; } @@ -68,7 +70,7 @@ export default class SSOSignUpPage extends React.Component { } var disabled = false; - if (this.state.name.length <= 3) { + if (this.state.name.length <= 2) { disabled = true; } diff --git a/web/react/components/user_settings/import_theme_modal.jsx b/web/react/components/user_settings/import_theme_modal.jsx index 48be83afe..0f5cb59f5 100644 --- a/web/react/components/user_settings/import_theme_modal.jsx +++ b/web/react/components/user_settings/import_theme_modal.jsx @@ -140,7 +140,7 @@ export default class ImportThemeModal extends React.Component { > <Modal.Body> <p> - {'To import a theme, go to a Slack team and look for “”Preferences” -> Sidebar Theme”. Open the custom theme option, copy the theme color values and paste them here:'} + {'To import a theme, go to a Slack team and look for “Preferences -> Sidebar Theme”. Open the custom theme option, copy the theme color values and paste them here:'} </p> <div className='form-group less'> <div className='col-sm-9'> diff --git a/web/react/components/user_settings/manage_incoming_hooks.jsx b/web/react/components/user_settings/manage_incoming_hooks.jsx index 1bbfbd162..fa2e2e5e4 100644 --- a/web/react/components/user_settings/manage_incoming_hooks.jsx +++ b/web/react/components/user_settings/manage_incoming_hooks.jsx @@ -21,7 +21,7 @@ export default class ManageIncomingHooks extends React.Component { this.getHooks(); } addNewHook() { - let hook = {}; //eslint-disable-line prefer-const + const hook = {}; hook.channel_id = this.state.channelId; Client.addIncomingHook( @@ -40,13 +40,13 @@ export default class ManageIncomingHooks extends React.Component { ); } removeHook(id) { - let data = {}; //eslint-disable-line prefer-const + const data = {}; data.id = id; Client.deleteIncomingHook( data, () => { - let hooks = this.state.hooks; //eslint-disable-line prefer-const + const hooks = this.state.hooks; let index = -1; for (let i = 0; i < hooks.length; i++) { if (hooks[i].id === id) { @@ -69,7 +69,7 @@ export default class ManageIncomingHooks extends React.Component { getHooks() { Client.listIncomingHooks( (data) => { - let state = this.state; //eslint-disable-line prefer-const + const state = this.state; if (data) { state.hooks = data; @@ -93,9 +93,11 @@ export default class ManageIncomingHooks extends React.Component { } const channels = ChannelStore.getAll(); - let options = []; //eslint-disable-line prefer-const + const options = []; channels.forEach((channel) => { - options.push(<option value={channel.id}>{channel.name}</option>); + if (channel.type !== Constants.DM_CHANNEL) { + options.push(<option value={channel.id}>{channel.name}</option>); + } }); let disableButton = ''; @@ -103,7 +105,7 @@ export default class ManageIncomingHooks extends React.Component { disableButton = ' disable'; } - let hooks = []; //eslint-disable-line prefer-const + const hooks = []; this.state.hooks.forEach((hook) => { const c = ChannelStore.get(hook.channel_id); hooks.push( diff --git a/web/react/components/user_settings/user_settings_notifications.jsx b/web/react/components/user_settings/user_settings_notifications.jsx index ba14f019f..42c65ef5d 100644 --- a/web/react/components/user_settings/user_settings_notifications.jsx +++ b/web/react/components/user_settings/user_settings_notifications.jsx @@ -420,7 +420,7 @@ export default class NotificationsTab extends React.Component { </label> <br/> </div> - <div><br/>{'Email notifications are sent for mentions and direct messages after you have been away from ' + global.window.config.SiteName + ' for 5 minutes.'}</div> + <div><br/>{'Email notifications are sent for mentions and direct messages after you’ve been offline for more than 60 seconds or away from ' + global.window.config.SiteName + ' for more than 5 minutes.'}</div> </div> ); diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx index 75e80bc7e..da59f8e5a 100644 --- a/web/react/utils/constants.jsx +++ b/web/react/utils/constants.jsx @@ -111,6 +111,7 @@ module.exports = { ], MONTHS: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], MAX_DMS: 20, + DM_CHANNEL: 'D', MAX_POST_LEN: 4000, EMOJI_SIZE: 16, ONLINE_ICON_SVG: "<svg version='1.1' id='Layer_1' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:cc='http://creativecommons.org/ns#' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:svg='http://www.w3.org/2000/svg' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' sodipodi:docname='TRASH_1_4.svg' inkscape:version='0.48.4 r9939' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='12px' height='12px' viewBox='0 0 12 12' enable-background='new 0 0 12 12' xml:space='preserve'><sodipodi:namedview inkscape:cy='139.7898' inkscape:cx='26.358185' inkscape:zoom='1.18' showguides='true' showgrid='false' id='namedview6' guidetolerance='10' gridtolerance='10' objecttolerance='10' borderopacity='1' bordercolor='#666666' pagecolor='#ffffff' inkscape:current-layer='Layer_1' inkscape:window-maximized='1' inkscape:window-y='-8' inkscape:window-x='-8' inkscape:window-height='705' inkscape:window-width='1366' inkscape:guide-bbox='true' inkscape:pageshadow='2' inkscape:pageopacity='0'><sodipodi:guide position='50.036793,85.991376' orientation='1,0' id='guide2986'></sodipodi:guide><sodipodi:guide position='58.426196,66.216355' orientation='0,1' id='guide3047'></sodipodi:guide></sodipodi:namedview><g><g><path class='online--icon' d='M6,5.487c1.371,0,2.482-1.116,2.482-2.493c0-1.378-1.111-2.495-2.482-2.495S3.518,1.616,3.518,2.994C3.518,4.371,4.629,5.487,6,5.487z M10.452,8.545c-0.101-0.829-0.36-1.968-0.726-2.541C9.475,5.606,8.5,5.5,8.5,5.5S8.43,7.521,6,7.521C3.507,7.521,3.5,5.5,3.5,5.5S2.527,5.606,2.273,6.004C1.908,6.577,1.648,7.716,1.547,8.545C1.521,8.688,1.49,9.082,1.498,9.142c0.161,1.295,2.238,2.322,4.375,2.358C5.916,11.501,5.958,11.501,6,11.501c0.043,0,0.084,0,0.127-0.001c2.076-0.026,4.214-1.063,4.375-2.358C10.509,9.082,10.471,8.696,10.452,8.545z'/></g></g></svg>", diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index 556f3967c..8b20e2adf 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -78,6 +78,14 @@ export function isAdmin(roles) { return false; } +export function isSystemAdmin(roles) { + if (isInRole(roles, 'system_admin')) { + return true; + } + + return false; +} + export function getDomainWithOutSub() { var parts = window.location.host.split('.'); @@ -255,163 +263,6 @@ export function escapeRegExp(string) { return string.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1'); } -function handleYoutubeTime(link) { - var timeRegex = /[\\?&]t=([0-9hms]+)/; - - var time = link.trim().match(timeRegex); - if (!time || !time[1]) { - return ''; - } - - var hours = time[1].match(/([0-9]+)h/); - var minutes = time[1].match(/([0-9]+)m/); - var seconds = time[1].match(/([0-9]+)s/); - - var ticks = 0; - - if (hours && hours[1]) { - ticks += parseInt(hours[1], 10) * 3600; - } - - if (minutes && minutes[1]) { - ticks += parseInt(minutes[1], 10) * 60; - } - - if (seconds && seconds[1]) { - ticks += parseInt(seconds[1], 10); - } - - return '&start=' + ticks.toString(); -} - -function getYoutubeEmbed(link) { - var regex = /.*(?:youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|watch\?(?:[a-zA-Z-_]+=[a-zA-Z0-9-_]+&)+v=)([^#\&\?]*).*/; - - var youtubeId = link.trim().match(regex)[1]; - var time = handleYoutubeTime(link); - - function onClick(e) { - var div = $(e.target).closest('.video-thumbnail__container')[0]; - var iframe = document.createElement('iframe'); - iframe.setAttribute('src', - 'https://www.youtube.com/embed/' + - div.id + - '?autoplay=1&autohide=1&border=0&wmode=opaque&fs=1&enablejsapi=1' + - time); - iframe.setAttribute('width', '480px'); - iframe.setAttribute('height', '360px'); - iframe.setAttribute('type', 'text/html'); - iframe.setAttribute('frameborder', '0'); - iframe.setAttribute('allowfullscreen', 'allowfullscreen'); - - div.parentNode.replaceChild(iframe, div); - } - - function success(data) { - if (!data.items.length || !data.items[0].snippet) { - return; - } - var metadata = data.items[0].snippet; - $('.video-type.' + youtubeId).html('Youtube - '); - $('.video-uploader.' + youtubeId).html(metadata.channelTitle); - $('.video-title.' + youtubeId).find('a').html(metadata.title); - } - - if (global.window.config.GoogleDeveloperKey) { - $.ajax({ - async: true, - url: 'https://www.googleapis.com/youtube/v3/videos', - type: 'GET', - data: {part: 'snippet', id: youtubeId, key: global.window.config.GoogleDeveloperKey}, - success: success - }); - } - - return ( - <div className='post-comment'> - <h4> - <span className={'video-type ' + youtubeId}>YouTube</span> - <span className={'video-title ' + youtubeId}><a href={link}></a></span> - </h4> - <h4 className={'video-uploader ' + youtubeId}></h4> - <div - className='video-div embed-responsive-item' - id={youtubeId} - onClick={onClick} - > - <div className='embed-responsive embed-responsive-4by3 video-div__placeholder'> - <div - id={youtubeId} - className='video-thumbnail__container' - > - <img - className='video-thumbnail' - src={'https://i.ytimg.com/vi/' + youtubeId + '/hqdefault.jpg'} - /> - <div className='block'> - <span className='play-button'><span/></span> - </div> - </div> - </div> - </div> - </div> - ); -} - -export function getEmbed(link) { - var ytRegex = /.*(?:youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|watch\?(?:[a-zA-Z-_]+=[a-zA-Z0-9-_]+&)+v=)([^#\&\?]*).*/; - - var match = link.trim().match(ytRegex); - if (match && match[1].length === 11) { - return getYoutubeEmbed(link); - } - - // Generl embed feature turned off for now - return ''; - - // NEEDS REFACTORING WHEN TURNED BACK ON - /* - var id = parseInt((Math.random() * 1000000) + 1); - - $.ajax({ - type: 'GET', - url: 'https://query.yahooapis.com/v1/public/yql', - data: { - q: 'select * from html where url="' + link + "\" and xpath='html/head'", - format: 'json' - }, - async: true - }).done(function(data) { - if(!data.query.results) { - return; - } - - var headerData = data.query.results.head; - - var description = '' - for(var i = 0; i < headerData.meta.length; i++) { - if(headerData.meta[i].name && (headerData.meta[i].name === 'description' || headerData.meta[i].name === 'Description')){ - description = headerData.meta[i].content; - break; - } - } - - $('.embed-title.'+id).html(headerData.title); - $('.embed-description.'+id).html(description); - }) - - return ( - <div className='post-comment'> - <div className={'web-embed-data'}> - <p className={'embed-title ' + id} /> - <p className={'embed-description ' + id} /> - <p className={'embed-link ' + id}>{link}</p> - </div> - </div> - ); - */ -} - export function areStatesEqual(state1, state2) { return JSON.stringify(state1) === JSON.stringify(state2); } @@ -607,7 +458,7 @@ export function applyTheme(theme) { } if (theme.centerChannelBg) { - changeCss('.app__content, .markdown__table, .markdown__table tbody tr', 'background:' + theme.centerChannelBg, 1); + changeCss('.app__content, .markdown__table, .markdown__table tbody tr, .command-box', 'background:' + theme.centerChannelBg, 1); changeCss('#post-list .post-list-holder-by-time', 'background:' + theme.centerChannelBg, 1); changeCss('#post-create', 'background:' + theme.centerChannelBg, 1); changeCss('.date-separator .separator__text, .new-separator .separator__text', 'background:' + theme.centerChannelBg, 1); @@ -616,19 +467,23 @@ export function applyTheme(theme) { } if (theme.centerChannelColor) { - changeCss('.app__content, .post-create__container .post-create-body .btn-file, .post-create__container .post-create-footer .msg-typing, .loading-screen .loading__content .round', 'color:' + theme.centerChannelColor, 1); + changeCss('.app__content, .post-create__container .post-create-body .btn-file, .post-create__container .post-create-footer .msg-typing, .loading-screen .loading__content .round, .command-name', 'color:' + theme.centerChannelColor, 1); changeCss('#post-create', 'color:' + theme.centerChannelColor, 2); + changeCss('.mentions--top, .command-box', 'box-shadow:' + changeOpacity(theme.centerChannelColor, 0.2) + ' 1px -3px 12px', 3); + changeCss('.mentions--top, .command-box', '-webkit-box-shadow:' + changeOpacity(theme.centerChannelColor, 0.2) + ' 1px -3px 12px', 2); + changeCss('.mentions--top, .command-box', '-moz-box-shadow:' + changeOpacity(theme.centerChannelColor, 0.2) + ' 1px -3px 12px', 1); changeCss('.post-body hr', 'background:' + theme.centerChannelColor, 1); changeCss('.channel-header .heading', 'color:' + theme.centerChannelColor, 1); changeCss('.markdown__table tbody tr:nth-child(2n)', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1); changeCss('.channel-header__info>div.dropdown .header-dropdown__icon', 'color:' + changeOpacity(theme.centerChannelColor, 0.8), 1); changeCss('.channel-header #member_popover', 'color:' + changeOpacity(theme.centerChannelColor, 0.8), 1); - changeCss('.custom-textarea, .custom-textarea:focus, .preview-container .preview-div, .post-image__column .post-image__details, .sidebar--right .sidebar-right__body, .markdown__table th, .markdown__table td', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1); + changeCss('.custom-textarea, .custom-textarea:focus, .preview-container .preview-div, .post-image__column .post-image__details, .sidebar--right .sidebar-right__body, .markdown__table th, .markdown__table td, .command-box', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1); + changeCss('.command-name', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1); changeCss('.custom-textarea', 'color:' + theme.centerChannelColor, 1); changeCss('.post-image__column', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 2); changeCss('.post-image__column .post-image__details', 'color:' + theme.centerChannelColor, 2); changeCss('.post-image__column a, .post-image__column a:hover, .post-image__column a:focus', 'color:' + theme.centerChannelColor, 1); - changeCss('.search-bar__container .search__form .search-bar', 'background:' + changeOpacity(theme.centerChannelColor, 0.2), 1); + changeCss('@media(max-width: 768px){.search-bar__container .search__form .search-bar', 'background:' + changeOpacity(theme.centerChannelColor, 0.2), 1); changeCss('.search-bar__container .search__form', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1); changeCss('.channel-intro .channel-intro__content', 'background:' + changeOpacity(theme.centerChannelColor, 0.05), 1); changeCss('.date-separator .separator__text', 'color:' + theme.centerChannelColor, 2); @@ -641,7 +496,7 @@ export function applyTheme(theme) { changeCss('@media(max-width: 1800px){.inner__wrap.move--left .post.post--comment.same--root', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.07), 2); changeCss('.post:hover, .sidebar--right .sidebar--right__header', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1); changeCss('.date-separator.hovered--before:after, .new-separator.hovered--before:after', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1); - changeCss('.date-separator.hovered--after:before, .new-separator.hovered--after:before', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1); + changeCss('.date-separator.hovered--after:before, .new-separator.hovered--after:before, .command-name:hover', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1); changeCss('.post.current--user:hover .post-body ', 'background: none;', 1); changeCss('.sidebar--right', 'color:' + theme.centerChannelColor, 2); } @@ -789,16 +644,12 @@ export function isValidUsername(name) { return error; } -export function updateTabTitle(name) { - document.title = name + ' ' + document.title.substring(document.title.lastIndexOf('-')); -} - export function updateAddressBar(channelName) { var teamURL = window.location.href.split('/channels')[0]; history.replaceState('data', '', teamURL + '/channels/' + channelName); } -export function switchChannel(channel, teammateName) { +export function switchChannel(channel) { AppDispatcher.handleViewAction({ type: ActionTypes.CLICK_CHANNEL, name: channel.name, @@ -807,12 +658,6 @@ export function switchChannel(channel, teammateName) { updateAddressBar(channel.name); - if (channel.type === 'D' && teammateName) { - updateTabTitle(teammateName); - } else { - updateTabTitle(channel.display_name); - } - AsyncClient.getChannels(true, true, true); AsyncClient.getChannelExtraInfo(true); AsyncClient.getPosts(channel.id); diff --git a/web/sass-files/sass/partials/_command-box.scss b/web/sass-files/sass/partials/_command-box.scss index 44eb9b8df..f1aa4dca2 100644 --- a/web/sass-files/sass/partials/_command-box.scss +++ b/web/sass-files/sass/partials/_command-box.scss @@ -4,20 +4,29 @@ width: 100%; border: $border-gray; bottom: 38px; + overflow: auto; @extend %popover-box-shadow; + .sidebar--right & { + bottom: 100px; + } } .command-name { position: relative; width: 100%; - background-color: #fff; - height: 37px; - line-height: 37px; - padding: 2px 10px 2px 5px; + line-height: 24px; + padding: 5px 10px 8px; z-index: 101; + font-size: 0.95em; + border-bottom: 1px solid #ddd; &:hover { background-color: #e8eaed; } + .command__desc { + margin-left: 5px; + @include opacity(0.5); + line-height: normal; + } } .command-desc { diff --git a/web/web.go b/web/web.go index da7eff13d..bf985a5a0 100644 --- a/web/web.go +++ b/web/web.go @@ -850,7 +850,12 @@ func incomingWebhook(c *api.Context, w http.ResponseWriter, r *http.Request) { r.ParseForm() - props := model.MapFromJson(strings.NewReader(r.FormValue("payload"))) + var props map[string]string + if r.Header.Get("Content-Type") == "application/json" { + props = model.MapFromJson(r.Body) + } else { + props = model.MapFromJson(strings.NewReader(r.FormValue("payload"))) + } text := props["text"] if len(text) == 0 { |