diff options
Diffstat (limited to 'Godeps/_workspace/src/code.google.com/p/graphics-go/graphics/detect')
10 files changed, 809 insertions, 0 deletions
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) + } + } +} |