File size: 3,352 Bytes
37e3d5a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#!/usr/bin/env python3
"""Reference-grounded gradient-stop extractor.

Synthetic 3-zone gradient PNG (violet → blue → navy on white bg) → assert the extractor
recovers the correct hue zones, names them, and flags the blue-leaning violet (B > R) as
blue-collapse. Deterministic across runs. Pure stdlib.
"""
from __future__ import annotations

import struct
import sys
import tempfile
import unittest
import zlib
from pathlib import Path

INTAKE = Path(__file__).resolve().parent.parent / "stage1_intake"
sys.path.insert(0, str(INTAKE))
from extract_gradient_stops import extract_gradient_stops, hue_name  # noqa: E402


def write_rgb_png(path: Path, width: int, height: int, fn) -> None:
    raw = bytearray()
    for y in range(height):
        raw.append(0)
        for x in range(width):
            raw += bytes(fn(x, y))

    def chunk(tag: bytes, data: bytes) -> bytes:
        return struct.pack(">I", len(data)) + tag + data + struct.pack(">I", zlib.crc32(tag + data) & 0xFFFFFFFF)

    png = (
        b"\x89PNG\r\n\x1a\n"
        + chunk(b"IHDR", struct.pack(">IIBBBBB", width, height, 8, 2, 0, 0, 0))
        + chunk(b"IDAT", zlib.compress(bytes(raw), 9))
        + chunk(b"IEND", b"")
    )
    path.write_bytes(png)


class GradientStopsTest(unittest.TestCase):
    def setUp(self) -> None:
        self.dir = Path(tempfile.mkdtemp())
        self.img = self.dir / "grad.png"
        W, H = 96, 48
        VIOLET = (150, 70, 210)   # B > R  → blue-collapse risk, hue ~274° = violet
        BLUE = (46, 108, 236)     # hue ~221° = blue
        NAVY = (8, 10, 30)        # dark blue
        WHITE = (255, 255, 255)

        def px(x, y):
            if y < 8 or y >= 40:
                return WHITE  # white border → corner-background = white, bar = foreground
            if x < 32:
                return VIOLET
            if x < 64:
                return BLUE
            return NAVY

        write_rgb_png(self.img, W, H, px)

    def test_hue_name_boundaries(self):
        self.assertEqual(hue_name(274.0), "violet")
        self.assertEqual(hue_name(221.0), "blue")
        self.assertEqual(hue_name(5.0), "red")

    def test_extracts_three_zones_in_order(self):
        result = extract_gradient_stops(self.img, axis="u", stops=6)
        names = [z["hueName"] for z in result["hueZones"]]
        # violet must appear before blue (order preserved along the axis)
        self.assertIn("violet", names)
        self.assertIn("blue", names)
        self.assertLess(names.index("violet"), names.index("blue"))

    def test_flags_blue_collapse_on_violet(self):
        result = extract_gradient_stops(self.img, axis="u", stops=6)
        violet_stops = [s for s in result["stops"] if s["hueName"] == "violet"]
        self.assertTrue(violet_stops, "expected at least one violet stop")
        v = violet_stops[0]
        self.assertEqual(v.get("hueRisk"), "blue-collapse")
        # suggested correction is magenta-leaning: R >= B
        self.assertGreaterEqual(v["suggestedRgb"][0], v["suggestedRgb"][2])

    def test_deterministic(self):
        a = extract_gradient_stops(self.img, axis="u", stops=6)
        b = extract_gradient_stops(self.img, axis="u", stops=6)
        self.assertEqual([s["rgb"] for s in a["stops"]], [s["rgb"] for s in b["stops"]])


if __name__ == "__main__":
    unittest.main(verbosity=2)