From 3cbfd087ee114c8d6da9ac1864c525913414962b Mon Sep 17 00:00:00 2001 From: Nigel Tao Date: Thu, 30 Jun 2016 14:37:17 +1000 Subject: [PATCH] shiny/imageutil: new package. Add an imageutil.Border function, for drawing outlined-but-not-filled rectangles, such as widget borders, checkboxes, table cells, etc. Change-Id: I1e55e5fe492a5d2bb4df6f03ab8d14c9cb9db67d Reviewed-on: https://go-review.googlesource.com/24632 Reviewed-by: David Crawshaw --- shiny/example/basic/main.go | 8 ++- shiny/imageutil/imageutil.go | 101 ++++++++++++++++++++++++++++++ shiny/imageutil/imageutil_test.go | 74 ++++++++++++++++++++++ 3 files changed, 181 insertions(+), 2 deletions(-) create mode 100644 shiny/imageutil/imageutil.go create mode 100644 shiny/imageutil/imageutil_test.go diff --git a/shiny/example/basic/main.go b/shiny/example/basic/main.go index a82f187cb..fe789fa9f 100644 --- a/shiny/example/basic/main.go +++ b/shiny/example/basic/main.go @@ -19,6 +19,7 @@ import ( "math" "golang.org/x/exp/shiny/driver" + "golang.org/x/exp/shiny/imageutil" "golang.org/x/exp/shiny/screen" "golang.org/x/image/math/f64" "golang.org/x/mobile/event/key" @@ -85,8 +86,11 @@ func main() { } case paint.Event: - w.Fill(sz.Bounds(), blue0, screen.Src) - w.Fill(sz.Bounds().Inset(10), blue1, screen.Src) + const inset = 10 + for _, r := range imageutil.Border(sz.Bounds(), inset) { + w.Fill(r, blue0, screen.Src) + } + w.Fill(sz.Bounds().Inset(inset), blue1, screen.Src) w.Upload(image.Point{20, 0}, b, b.Bounds()) w.Fill(image.Rect(50, 50, 350, 120), red, screen.Over) diff --git a/shiny/imageutil/imageutil.go b/shiny/imageutil/imageutil.go new file mode 100644 index 000000000..43b23099f --- /dev/null +++ b/shiny/imageutil/imageutil.go @@ -0,0 +1,101 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package imageutil implements some image utility functions. +package imageutil + +import ( + "image" +) + +// TODO: move Border into the standard library's package image? + +// Border returns four rectangles that together contain those points between r +// and r.Inset(inset). Visually: +// +// 00000000 +// 00000000 +// 11....22 +// 11....22 +// 11....22 +// 33333333 +// 33333333 +// +// The inset may be negative, in which case the points will be outside r. +// +// Some of the returned rectangles may be empty. None of the returned +// rectangles will overlap. +func Border(r image.Rectangle, inset int) [4]image.Rectangle { + if inset == 0 { + return [4]image.Rectangle{} + } + if r.Dx() <= 2*inset || r.Dy() <= 2*inset { + return [4]image.Rectangle{r} + } + + x := [4]int{ + r.Min.X, + r.Min.X + inset, + r.Max.X - inset, + r.Max.X, + } + y := [4]int{ + r.Min.Y, + r.Min.Y + inset, + r.Max.Y - inset, + r.Max.Y, + } + if inset < 0 { + x[0], x[1] = x[1], x[0] + x[2], x[3] = x[3], x[2] + y[0], y[1] = y[1], y[0] + y[2], y[3] = y[3], y[2] + } + + // The top and bottom sections are responsible for filling the corners. + // The top and bottom sections go from x[0] to x[3], across the y's. + // The left and right sections go from y[1] to y[2], across the x's. + + return [4]image.Rectangle{{ + // Top section. + Min: image.Point{ + X: x[0], + Y: y[0], + }, + Max: image.Point{ + X: x[3], + Y: y[1], + }, + }, { + // Left section. + Min: image.Point{ + X: x[0], + Y: y[1], + }, + Max: image.Point{ + X: x[1], + Y: y[2], + }, + }, { + // Right section. + Min: image.Point{ + X: x[2], + Y: y[1], + }, + Max: image.Point{ + X: x[3], + Y: y[2], + }, + }, { + // Bottom section. + Min: image.Point{ + X: x[0], + Y: y[2], + }, + Max: image.Point{ + X: x[3], + Y: y[3], + }, + }} +} diff --git a/shiny/imageutil/imageutil_test.go b/shiny/imageutil/imageutil_test.go new file mode 100644 index 000000000..92722940c --- /dev/null +++ b/shiny/imageutil/imageutil_test.go @@ -0,0 +1,74 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package imageutil + +import ( + "image" + "testing" +) + +func area(r image.Rectangle) int { + dx, dy := r.Dx(), r.Dy() + if dx <= 0 || dy <= 0 { + return 0 + } + return dx * dy +} + +func TestBorder(t *testing.T) { + r := image.Rect(100, 200, 400, 300) + + insets := []int{ + -100, + -1, + +0, + +1, + +20, + +49, + +50, + +51, + +149, + +150, + +151, + } + + for _, inset := range insets { + border := Border(r, inset) + + outer, inner := r, r.Inset(inset) + if inset < 0 { + outer, inner = inner, outer + } + + got := 0 + for _, b := range border { + got += area(b) + } + want := area(outer) - area(inner) + if got != want { + t.Errorf("inset=%d: total area: got %d, want %d", inset, got, want) + } + + for i, bi := range border { + for j, bj := range border { + if i <= j { + continue + } + if !bi.Intersect(bj).Empty() { + t.Errorf("inset=%d: %v and %v overlap", inset, bi, bj) + } + } + } + + for _, b := range border { + if got := outer.Intersect(b); got != b { + t.Errorf("inset=%d: outer intersection: got %v, want %v", inset, got, b) + } + if got := inner.Intersect(b); !got.Empty() { + t.Errorf("inset=%d: inner intersection: got %v, want empty", inset, got) + } + } + } +}