package pixel_test import ( "testing" "git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel" ) func TestRectAndLines(t *testing.T) { d := canvas(t, 8, 6, func(g pixel.Canvas) { g.Rect(1, 1, 3, 2, pixel.Accent) g.HLine(0, 4, 5, pixel.Good) g.VLine(7, 0, 6, pixel.Bad) g.Dot(6, 5, pixel.Magic) g.Rect(5, 1, 0, 3, pixel.Ink) }) if d.count(1, 1, 3, 2, pixel.Accent) != 6 || d.at(0, 1) != pixel.Back || d.at(4, 2) != pixel.Back { t.Fatal("Rect covers the wrong dots") } if d.count(0, 4, 5, 1, pixel.Good) != 5 || d.at(5, 4) != pixel.Back { t.Fatal("HLine covers the wrong dots") } if d.count(7, 0, 1, 6, pixel.Bad) != 6 || d.at(6, 5) != pixel.Magic { t.Fatal("VLine or Dot wrong") } if d.count(5, 1, 1, 3, pixel.Ink) != 0 { t.Fatal("zero-width Rect drew") } } func TestLineBresenham(t *testing.T) { cases := [][4]int{{0, 0, 9, 3}, {9, 0, 0, 3}, {2, 0, 4, 9}, {0, 9, 6, 0}, {3, 3, 3, 3}, {0, 5, 9, 5}} for _, c := range cases { d := canvas(t, 10, 10, func(g pixel.Canvas) { g.Line(c[0], c[1], c[2], c[3], pixel.Ink) }) if d.at(c[0], c[1]) != pixel.Ink || d.at(c[2], c[3]) != pixel.Ink { t.Fatalf("line %v misses an end", c) } dx, dy := c[2]-c[0], c[3]-c[1] major := max(dx, -dx, dy, -dy) if got := d.count(0, 0, 10, 10, pixel.Ink); got != major+1 { t.Fatalf("line %v has %d dots, want %d", c, got, major+1) } xMajor := max(dx, -dx) >= max(dy, -dy) for i := range 10 { var n int if xMajor { n = d.count(i, 0, 1, 10, pixel.Ink) } else { n = d.count(0, i, 10, 1, pixel.Ink) } inside := i >= min(c[0], c[2]) && i <= max(c[0], c[2]) if !xMajor { inside = i >= min(c[1], c[3]) && i <= max(c[1], c[3]) } if inside != (n == 1) || n > 1 { t.Fatalf("line %v: %d dots across major step %d", c, n, i) } } } } func TestLineTieBreak(t *testing.T) { d := canvas(t, 3, 2, func(g pixel.Canvas) { g.Line(0, 0, 2, 1, pixel.Ink) }) for _, p := range [][2]int{{0, 0}, {1, 1}, {2, 1}} { if d.at(p[0], p[1]) != pixel.Ink { t.Fatalf("Line(0,0,2,1) misses %v", p) } } if d.at(1, 0) != pixel.Back { t.Fatal("Line(0,0,2,1) took the wrong tie") } } func TestCircleAndDisc(t *testing.T) { const cx, cy, r = 7, 7, 5 disc := canvas(t, 15, 15, func(g pixel.Canvas) { g.Disc(cx, cy, r, pixel.Info) }) ring := canvas(t, 15, 15, func(g pixel.Canvas) { g.Circle(cx, cy, r, pixel.Ink) }) for _, p := range [][2]int{{cx + r, cy}, {cx - r, cy}, {cx, cy + r}, {cx, cy - r}} { if disc.at(p[0], p[1]) != pixel.Info || ring.at(p[0], p[1]) != pixel.Ink { t.Fatalf("radius point %v missing", p) } } if disc.at(cx, cy) != pixel.Info || ring.at(cx, cy) != pixel.Back || disc.at(cx+r, cy+r) != pixel.Back { t.Fatal("disc must be solid, circle hollow, corners empty") } for y := range 15 { for x := range 15 { if disc.at(x, y) != disc.at(2*cx-x, y) || disc.at(x, y) != disc.at(x, 2*cy-y) || disc.at(x, y) != disc.at(y, x) { t.Fatalf("disc not symmetric at %d,%d", x, y) } if ring.at(x, y) == pixel.Ink && disc.at(x, y) != pixel.Info { t.Fatalf("circle dot %d,%d outside the disc", x, y) } } } } func TestCircleExact(t *testing.T) { ring := canvas(t, 7, 7, func(g pixel.Canvas) { g.Circle(3, 3, 3, pixel.Ink) }) disc := canvas(t, 7, 7, func(g pixel.Canvas) { g.Disc(3, 3, 3, pixel.Ink) }) wantRing := []string{"..###..", ".#...#.", "#.....#", "#.....#", "#.....#", ".#...#.", "..###.."} wantDisc := []string{"..###..", ".#####.", "#######", "#######", "#######", ".#####.", "..###.."} for y := range 7 { for x := range 7 { if (ring.at(x, y) == pixel.Ink) != (wantRing[y][x] == '#') || (disc.at(x, y) == pixel.Ink) != (wantDisc[y][x] == '#') { t.Fatalf("radius 3 dot %d,%d differs from the midpoint shape", x, y) } } } } func TestCircleRadiusSix(t *testing.T) { octant := [][2]int{{6, 0}, {6, 1}, {6, 2}, {5, 3}, {4, 4}} want := map[[2]int]bool{} for _, p := range octant { for _, q := range [][2]int{{p[0], p[1]}, {p[1], p[0]}} { for _, s := range [][2]int{{1, 1}, {-1, 1}, {1, -1}, {-1, -1}} { want[[2]int{6 + s[0]*q[0], 6 + s[1]*q[1]}] = true } } } d := canvas(t, 13, 13, func(g pixel.Canvas) { g.Circle(6, 6, 6, pixel.Ink) }) for y := range 13 { for x := range 13 { if (d.at(x, y) == pixel.Ink) != want[[2]int{x, y}] { t.Fatalf("radius 6 dot %d,%d differs from the midpoint shape", x, y) } } } } func TestBayerMatrix(t *testing.T) { want := [4][4]int{{0, 8, 2, 10}, {12, 4, 14, 6}, {3, 11, 1, 9}, {15, 7, 13, 5}} first := [4][4]int{} for level := 16; level >= 1; level-- { d := canvas(t, 4, 4, func(g pixel.Canvas) { g.Veil(0, 0, 4, 4, pixel.Ink, level) }) for y := range 4 { for x := range 4 { if d.at(x, y) == pixel.Ink { first[y][x] = level - 1 } } } } if first != want { t.Fatalf("Bayer thresholds %v, want %v", first, want) } } func TestFrameStyles(t *testing.T) { const w, h = 8, 7 want := map[pixel.FrameStyle][][3]int{ pixel.Flat: {{0, 0, int(pixel.Edge)}, {1, 1, int(pixel.Face)}, {w - 1, h - 1, int(pixel.Edge)}}, pixel.Raised: {{0, 0, int(pixel.Edge)}, {1, 1, int(pixel.Lit)}, {w - 2, h - 2, int(pixel.Shade)}, {2, 2, int(pixel.Face)}}, pixel.Sunken: {{1, 1, int(pixel.Shade)}, {w - 2, h - 2, int(pixel.Lit)}, {2, 2, int(pixel.Face)}}, pixel.Double: {{0, 0, int(pixel.Edge)}, {1, 1, int(pixel.Face)}, {2, 2, int(pixel.Edge)}, {3, 3, int(pixel.Face)}}, pixel.Window: {{0, 0, int(pixel.Back)}, {1, 0, int(pixel.Edge)}, {1, 1, int(pixel.Lit)}, {2, 2, int(pixel.Shade)}, {3, 3, int(pixel.Face)}}, pixel.Notched: {{0, 0, int(pixel.Back)}, {1, 0, int(pixel.Edge)}, {0, 1, int(pixel.Edge)}, {1, 1, int(pixel.Face)}}, } for style, checks := range want { d := canvas(t, w, h, func(g pixel.Canvas) { g.Frame(0, 0, w, h, style, pixel.Face) }) for _, ck := range checks { if got := d.at(ck[0], ck[1]); got != pixel.Slot(ck[2]) { t.Errorf("style %d dot %d,%d = slot %d, want %d", style, ck[0], ck[1], got, ck[2]) } } } mustPanic(t, "2×2 frame", func() { canvas(t, 4, 4, func(g pixel.Canvas) { g.Frame(0, 0, 2, 2, pixel.Flat, pixel.Face) }) }) } func TestDitherLevels(t *testing.T) { for level := 0; level <= 16; level++ { d := canvas(t, 8, 8, func(g pixel.Canvas) { g.Dither(0, 0, 8, 8, pixel.Face, pixel.Ink, level) }) for _, b := range [][2]int{{0, 0}, {4, 0}, {0, 4}, {4, 4}} { if n := d.count(b[0], b[1], 4, 4, pixel.Ink); n != level { t.Fatalf("level %d: 4×4 block at %v has %d ink dots", level, b, n) } } } d := canvas(t, 6, 6, func(g pixel.Canvas) { g.Checker(0, 0, 6, 6, pixel.Ink) }) for y := range 6 { for x := range 6 { if want := (x+y)%2 == 0; (d.at(x, y) == pixel.Ink) != want { t.Fatalf("checker dot %d,%d", x, y) } } } } func TestDitherAnchorsToWindow(t *testing.T) { d := canvas(t, 8, 4, func(g pixel.Canvas) { g.Rect(0, 0, 8, 4, pixel.Accent) g.Veil(1, 0, 3, 4, pixel.Ink, 8) g.Veil(4, 1, 4, 3, pixel.Ink, 8) }) for y := range 4 { for x := 1; x < 8; x++ { if x >= 4 && y == 0 { continue } want := pixel.Accent if (x+y)%2 == 0 { want = pixel.Ink } if got := d.at(x, y); got != want { t.Fatalf("veil dot %d,%d = %d, want %d", x, y, got, want) } } } mustPanic(t, "level 17", func() { canvas(t, 2, 2, func(g pixel.Canvas) { g.Veil(0, 0, 1, 1, pixel.Ink, 17) }) }) } func TestTextAndMicroDraw(t *testing.T) { a := []string{"...#...", "...#...", "..#.#..", "..#.#..", ".#...#.", ".#####.", ".#...#.", "#.....#", "#.....#"} for _, scale := range []int{1, 2} { d := canvas(t, 8*scale, 16*scale, func(g pixel.Canvas) { g.Text(0, 0, "A", pixel.Ink, scale) }) for row, line := range a { for col, ch := range line { if got := d.at(col*scale, (4+row)*scale) == pixel.Ink; got != (ch == '#') { t.Fatalf("×%d 'A' dot %d,%d", scale, col, row) } } } if d.count(0, 0, 8*scale, 4*scale, pixel.Ink) != 0 { t.Fatalf("×%d 'A' drew above its top", scale) } } one := []string{".#.", "##.", ".#.", ".#.", "###"} d := canvas(t, 8, 5, func(g pixel.Canvas) { g.Micro(0, 0, "11", pixel.Good, 1) }) for row, line := range one { for col, ch := range line { if (d.at(col, row) == pixel.Good) != (ch == '#') || (d.at(col+4, row) == pixel.Good) != (ch == '#') { t.Fatalf("micro '1' dot %d,%d", col, row) } } } } func TestTextBlend(t *testing.T) { plain := canvas(t, 40, 20, func(g pixel.Canvas) { g.Text(2, 2, "HM", pixel.Ink, 1) }) blend := canvas(t, 40, 20, func(g pixel.Canvas) { g.TextBlend(2, 2, "HM", pixel.Accent, pixel.Good, 1) }) tops, bottoms := 0, 0 for y := range 20 { for x := range 40 { ink, got := plain.at(x, y) == pixel.Ink, blend.at(x, y) if ink != (got == pixel.Accent || got == pixel.Good) { t.Fatalf("dot %d,%d: text %v, blend %v", x, y, ink, got) } if got == pixel.Accent && y > 2+pixel.LineHeight*3/4 || got == pixel.Good && y < 2+pixel.LineHeight/4 { t.Fatalf("dot %d,%d has %v on the wrong end", x, y, got) } tops += boolInt(got == pixel.Accent) bottoms += boolInt(got == pixel.Good) } } if tops == 0 || bottoms == 0 { t.Fatalf("blend used %d top and %d bottom dots", tops, bottoms) } } func boolInt(b bool) int { if b { return 1 } return 0 }