""" tests/test_background.py -------------------------- Tests for border-connected background detection. Run with: pytest tests/test_background.py -v """ import sys from pathlib import Path import numpy as np sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) from background import apply_transparency, detect_background def test_black_background_detected(): rgba = np.zeros((20, 20, 4), dtype=np.uint8) rgba[:, :, 3] = 255 bg = detect_background(rgba) assert bg.color[:3] == (0, 0, 0) assert bg.is_transparent is False def test_interior_black_pixel_is_not_marked_background(): """ CRITICAL RULE: a sprite drawn on a black background can have its own black pixels (e.g. an outline). Only background pixels reachable from the sheet border are background -- an enclosed black pixel inside a sprite must never be marked as background. """ rgba = np.zeros((20, 20, 4), dtype=np.uint8) rgba[:, :, 3] = 255 rgba[5:15, 5:15] = [200, 50, 50, 255] # red sprite body rgba[9, 9] = [0, 0, 0, 255] # black pixel INSIDE the sprite bg = detect_background(rgba) assert bg.mask[9, 9] == False # noqa: E712 -- must not be flagged as background assert bg.mask[0, 0] == True # noqa: E712 -- the actual border IS background def test_apply_transparency_preserves_interior_pixel(): rgba = np.zeros((20, 20, 4), dtype=np.uint8) rgba[:, :, 3] = 255 rgba[5:15, 5:15] = [200, 50, 50, 255] rgba[9, 9] = [0, 0, 0, 255] bg = detect_background(rgba) out = apply_transparency(rgba, bg) assert out[9, 9, 3] == 255 # interior black pixel: untouched assert out[0, 0, 3] == 0 # border background pixel: made transparent assert tuple(out[9, 9, :3]) == (0, 0, 0) # color also untouched def test_already_transparent_sheet_detected_as_transparent_background(): rgba = np.zeros((20, 20, 4), dtype=np.uint8) # fully transparent rgba[5:15, 5:15] = [80, 180, 90, 255] # opaque sprite in the middle bg = detect_background(rgba) assert bg.is_transparent is True assert bg.mask[0, 0] == True # noqa: E712 def test_white_background_detected(): rgba = np.full((20, 20, 4), 255, dtype=np.uint8) rgba[5:15, 5:15] = [10, 10, 200, 255] # blue sprite bg = detect_background(rgba) assert bg.color[:3] == (255, 255, 255) assert bg.mask[9, 9] == False # noqa: E712 -- sprite interior untouched def test_colored_background_detected(): rgba = np.zeros((20, 20, 4), dtype=np.uint8) rgba[:, :, 3] = 255 rgba[:, :, :3] = [30, 200, 30] # green background rgba[5:15, 5:15] = [200, 30, 30, 255] # red sprite bg = detect_background(rgba) assert bg.color[:3] == (30, 200, 30) assert bg.mask[9, 9] == False # noqa: E712 def test_tolerance_absorbs_near_background_noise(): """ Slightly-off-black border pixels (e.g. from lossy JPEG compression artifacts) should still be classified as background within tolerance. """ rng = np.random.default_rng(0) rgba = np.zeros((20, 20, 4), dtype=np.uint8) rgba[:, :, 3] = 255 jitter = rng.integers(-5, 6, size=(20, 20, 3)) rgba[:, :, :3] = np.clip(0 + jitter, 0, 255).astype(np.uint8) rgba[5:15, 5:15] = [200, 50, 50, 255] bg = detect_background(rgba) # Most of the noisy near-black border should still register as background. assert bg.mask[0, 0] == True # noqa: E712 assert bg.mask.sum() > 200 # most of the 400px sheet minus the sprite def test_two_sprites_near_black_border_both_preserved(): """Two separate sprites each containing a black pixel of their own.""" rgba = np.zeros((30, 30, 4), dtype=np.uint8) rgba[:, :, 3] = 255 rgba[3:10, 3:10] = [80, 200, 90, 255] rgba[6, 6] = [0, 0, 0, 255] rgba[20:27, 20:27] = [90, 90, 220, 255] rgba[23, 23] = [0, 0, 0, 255] bg = detect_background(rgba) assert bg.mask[6, 6] == False # noqa: E712 assert bg.mask[23, 23] == False # noqa: E712 if __name__ == "__main__": import traceback tests = [(name, fn) for name, fn in list(globals().items()) if name.startswith("test_") and callable(fn)] passed, failed = 0, 0 for name, fn in tests: try: fn() print(f"PASS {name}") passed += 1 except Exception: print(f"FAIL {name}") traceback.print_exc() failed += 1 print(f"\n{passed} passed, {failed} failed") sys.exit(1 if failed else 0)