summaryrefslogtreecommitdiffstats
path: root/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics
diff options
context:
space:
mode:
Diffstat (limited to 'Godeps/_workspace/src/code.google.com/p/graphics-go/graphics')
-rw-r--r--Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/Makefile15
-rw-r--r--Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/affine.go174
-rw-r--r--Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/blur.go68
-rw-r--r--Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/blur_test.go207
-rw-r--r--Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/convolve/Makefile11
-rw-r--r--Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/convolve/convolve.go274
-rw-r--r--Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/convolve/convolve_test.go78
-rw-r--r--Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/Makefile15
-rw-r--r--Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/detect.go133
-rw-r--r--Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/detect_test.go77
-rw-r--r--Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/doc.go31
-rw-r--r--Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/integral.go93
-rw-r--r--Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/integral_test.go156
-rw-r--r--Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/opencv_parser.go125
-rw-r--r--Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/opencv_parser_test.go75
-rw-r--r--Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/projector.go55
-rw-r--r--Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/projector_test.go49
-rw-r--r--Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/graphicstest/Makefile11
-rw-r--r--Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/graphicstest/graphicstest.go112
-rw-r--r--Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/interp/Makefile13
-rw-r--r--Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/interp/bilinear.go206
-rw-r--r--Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/interp/bilinear_test.go143
-rw-r--r--Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/interp/doc.go25
-rw-r--r--Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/interp/interp.go29
-rw-r--r--Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/rotate.go35
-rw-r--r--Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/rotate_test.go169
-rw-r--r--Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/scale.go31
-rw-r--r--Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/scale_test.go153
-rw-r--r--Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/shared_test.go69
-rw-r--r--Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/thumbnail.go41
-rw-r--r--Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/thumbnail_test.go53
31 files changed, 2726 insertions, 0 deletions
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
new file mode 100644
index 000000000..28a06f0e8
--- /dev/null
+++ b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/Makefile
@@ -0,0 +1,15 @@
+# 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
new file mode 100644
index 000000000..0ac2ec9da
--- /dev/null
+++ b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/affine.go
@@ -0,0 +1,174 @@
+// 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
new file mode 100644
index 000000000..9a54d5ad5
--- /dev/null
+++ b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/blur.go
@@ -0,0 +1,68 @@
+// 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
new file mode 100644
index 000000000..1d84fa604
--- /dev/null
+++ b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/blur_test.go
@@ -0,0 +1,207 @@
+// 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
new file mode 100644
index 000000000..a5691fa30
--- /dev/null
+++ b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/convolve/Makefile
@@ -0,0 +1,11 @@
+# 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
new file mode 100644
index 000000000..da69496d0
--- /dev/null
+++ b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/convolve/convolve.go
@@ -0,0 +1,274 @@
+// 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
new file mode 100644
index 000000000..f34d7afc8
--- /dev/null
+++ b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/convolve/convolve_test.go
@@ -0,0 +1,78 @@
+// 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
new file mode 100644
index 000000000..0b1c6cb3e
--- /dev/null
+++ b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/Makefile
@@ -0,0 +1,15 @@
+# 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
new file mode 100644
index 000000000..dde941cbe
--- /dev/null
+++ b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/detect.go
@@ -0,0 +1,133 @@
+// 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
new file mode 100644
index 000000000..8a2df113d
--- /dev/null
+++ b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/detect_test.go
@@ -0,0 +1,77 @@
+// 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
new file mode 100644
index 000000000..a0f4e94cd
--- /dev/null
+++ b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/doc.go
@@ -0,0 +1,31 @@
+// 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
new file mode 100644
index 000000000..814ced590
--- /dev/null
+++ b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/integral.go
@@ -0,0 +1,93 @@
+// 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
new file mode 100644
index 000000000..0bc321a4d
--- /dev/null
+++ b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/integral_test.go
@@ -0,0 +1,156 @@
+// 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
new file mode 100644
index 000000000..51ded1a1c
--- /dev/null
+++ b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/opencv_parser.go
@@ -0,0 +1,125 @@
+// 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
new file mode 100644
index 000000000..343390499
--- /dev/null
+++ b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/opencv_parser_test.go
@@ -0,0 +1,75 @@
+// 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
new file mode 100644
index 000000000..1ebd6db59
--- /dev/null
+++ b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/projector.go
@@ -0,0 +1,55 @@
+// 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
new file mode 100644
index 000000000..c6d0b0cd5
--- /dev/null
+++ b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect/projector_test.go
@@ -0,0 +1,49 @@
+// 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
new file mode 100644
index 000000000..7bfdf22d8
--- /dev/null
+++ b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/graphicstest/Makefile
@@ -0,0 +1,11 @@
+# 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
new file mode 100644
index 000000000..ceb3a974d
--- /dev/null
+++ b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/graphicstest/graphicstest.go
@@ -0,0 +1,112 @@
+// 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
new file mode 100644
index 000000000..4d8f524fb
--- /dev/null
+++ b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/interp/Makefile
@@ -0,0 +1,13 @@
+# 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
new file mode 100644
index 000000000..e18321a15
--- /dev/null
+++ b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/interp/bilinear.go
@@ -0,0 +1,206 @@
+// 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
new file mode 100644
index 000000000..242d70546
--- /dev/null
+++ b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/interp/bilinear_test.go
@@ -0,0 +1,143 @@
+// 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
new file mode 100644
index 000000000..b115534cc
--- /dev/null
+++ b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/interp/doc.go
@@ -0,0 +1,25 @@
+// 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
new file mode 100644
index 000000000..560637d4a
--- /dev/null
+++ b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/interp/interp.go
@@ -0,0 +1,29 @@
+// 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
new file mode 100644
index 000000000..62bde1a08
--- /dev/null
+++ b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/rotate.go
@@ -0,0 +1,35 @@
+// 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
new file mode 100644
index 000000000..bfc532a0a
--- /dev/null
+++ b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/rotate_test.go
@@ -0,0 +1,169 @@
+// 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
new file mode 100644
index 000000000..7a7fe9696
--- /dev/null
+++ b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/scale.go
@@ -0,0 +1,31 @@
+// 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
new file mode 100644
index 000000000..9c2468f11
--- /dev/null
+++ b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/scale_test.go
@@ -0,0 +1,153 @@
+// 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
new file mode 100644
index 000000000..e1cd21fb3
--- /dev/null
+++ b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/shared_test.go
@@ -0,0 +1,69 @@
+// 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
new file mode 100644
index 000000000..d3ad7e8f7
--- /dev/null
+++ b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/thumbnail.go
@@ -0,0 +1,41 @@
+// 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
new file mode 100644
index 000000000..d12659f17
--- /dev/null
+++ b/Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/thumbnail_test.go
@@ -0,0 +1,53 @@
+// 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)
+ }
+}