diff options
70 files changed, 3782 insertions, 5459 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/file.go b/api/file.go index 1cb05e81b..be8fc5456 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/user.go b/api/user.go index 9ed4404f1..9718d534e 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" @@ -466,10 +466,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 + } } } } @@ -798,7 +802,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) 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/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..a48448cc5 --- /dev/null +++ b/doc/integrations/webhook/incoming.md @@ -0,0 +1,57 @@ +# 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."} +``` + +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/model/config.go b/model/config.go index 31c2b16dc..853e2bbc0 100644 --- a/model/config.go +++ b/model/config.go @@ -63,12 +63,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 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_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'> |