package hitbox import ( "bytes" "encoding/json" "image" "image/color" "testing" ) // sheet4 builds a 2x1 sheet of 4x4 cells: frame 0 has a 2x2 blob at // (1,1); frame 1 is empty. func sheet4() image.Image { img := image.NewNRGBA(image.Rect(0, 0, 8, 4)) for y := 1; y <= 2; y++ { for x := 1; x <= 2; x++ { img.SetNRGBA(x, y, color.NRGBA{255, 0, 0, 255}) } } return img } func TestScanTightBox(t *testing.T) { s, err := Scan(sheet4(), "test.png", 4, 4, 1, 0, 0) if err != nil { t.Fatal(err) } if s.Cols != 2 || s.Rows != 1 || len(s.Frames) != 2 { t.Fatalf("layout %dx%d frames=%d", s.Cols, s.Rows, len(s.Frames)) } f0 := s.Frames[0] if f0.Empty || f0.Box == nil { t.Fatal("frame 0 should have a box") } if *f0.Box != (Box{X: 1, Y: 1, W: 2, H: 2}) { t.Errorf("frame 0 box = %+v", *f0.Box) } f1 := s.Frames[1] if !f1.Empty || f1.Box != nil { t.Errorf("frame 1 should be empty, got %+v", f1) } } func TestScanWholeImageDefault(t *testing.T) { s, err := Scan(sheet4(), "x.png", 0, 0, 1, 0, 0) if err != nil { t.Fatal(err) } if len(s.Frames) != 1 || s.CellW != 8 || s.CellH != 4 { t.Errorf("whole-image scan: %+v", s) } } func TestScanErrors(t *testing.T) { if _, err := Scan(sheet4(), "x.png", 3, 4, 1, 0, 0); err == nil { t.Error("non-divisible cell size should error") } if _, err := Scan(sheet4(), "x.png", 4, 4, 0, 0, 0); err == nil { t.Error("threshold 0 should error") } } func TestShrinkAndPad(t *testing.T) { img := image.NewNRGBA(image.Rect(0, 0, 4, 4)) for y := 0; y < 4; y++ { for x := 0; x < 4; x++ { img.SetNRGBA(x, y, color.NRGBA{0, 0, 0, 255}) } } s, _ := Scan(img, "x.png", 4, 4, 1, 1, 0) // shrink 1 if *s.Frames[0].Box != (Box{X: 1, Y: 1, W: 2, H: 2}) { t.Errorf("shrunk box = %+v", *s.Frames[0].Box) } s, _ = Scan(img, "x.png", 4, 4, 1, 0, 3) // pad clamps to cell if *s.Frames[0].Box != (Box{X: 0, Y: 0, W: 4, H: 4}) { t.Errorf("padded box = %+v", *s.Frames[0].Box) } s, _ = Scan(img, "x.png", 4, 4, 1, 10, 0) // over-shrink -> min 1x1 if s.Frames[0].Box.W < 1 || s.Frames[0].Box.H < 1 { t.Errorf("over-shrunk box = %+v", *s.Frames[0].Box) } } func TestThreshold(t *testing.T) { img := image.NewNRGBA(image.Rect(0, 0, 2, 1)) img.SetNRGBA(0, 0, color.NRGBA{0, 0, 0, 100}) img.SetNRGBA(1, 0, color.NRGBA{0, 0, 0, 200}) s, _ := Scan(img, "x.png", 2, 1, 150, 0, 0) if *s.Frames[0].Box != (Box{X: 1, Y: 0, W: 1, H: 1}) { t.Errorf("threshold box = %+v", *s.Frames[0].Box) } s, _ = Scan(img, "x.png", 2, 1, 50, 0, 0) if *s.Frames[0].Box != (Box{X: 0, Y: 0, W: 2, H: 1}) { t.Errorf("low-threshold box = %+v", *s.Frames[0].Box) } } func TestLayoutFromName(t *testing.T) { w, h, c, r, ok := LayoutFromName("/tmp/walk_8x8_4x1.png") if !ok || w != 8 || h != 8 || c != 4 || r != 1 { t.Errorf("parsed %d %d %d %d ok=%v", w, h, c, r, ok) } if _, _, _, _, ok := LayoutFromName("plain.png"); ok { t.Error("plain.png should not match") } } func TestInferScale(t *testing.T) { // walk_8x8_4x1.png rendered at scale 8 -> 256x64 if s := InferScale(256, 64, 8, 8, 4, 1); s != 8 { t.Errorf("scale = %d, want 8", s) } if s := InferScale(32, 8, 8, 8, 4, 1); s != 1 { t.Errorf("unscaled = %d, want 1", s) } if s := InferScale(250, 64, 8, 8, 4, 1); s != 0 { t.Errorf("non-divisible = %d, want 0", s) } if s := InferScale(256, 32, 8, 8, 4, 1); s != 0 { t.Errorf("axis mismatch = %d, want 0", s) } } func TestJSONShape(t *testing.T) { s, _ := Scan(sheet4(), "test.png", 4, 4, 1, 0, 0) var buf bytes.Buffer if err := s.WriteJSON(&buf); err != nil { t.Fatal(err) } var back Sheet if err := json.Unmarshal(buf.Bytes(), &back); err != nil { t.Fatalf("invalid json: %v", err) } if back.Frames[0].Box.W != 2 { t.Errorf("json roundtrip box = %+v", back.Frames[0].Box) } }