Skip to content

Commit 8f17607

Browse files
authored
(building) Support more modes of creating the foundation (#10)
1 parent 863abeb commit 8f17607

File tree

4 files changed

+249
-156
lines changed

4 files changed

+249
-156
lines changed

agrf/graphics/cv/foundation.py

Lines changed: 78 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -2,137 +2,93 @@
22
import numpy as np
33
from .. import LayeredImage
44

5+
THIS_FILE = grf.PythonFile(__file__)
56

6-
def make_foundation_subimage(img: LayeredImage, scale, part, style, cut_inside, zshift, solid) -> LayeredImage:
7-
if img.alpha is not None:
8-
r = np.arange(img.h)[:, np.newaxis] + img.yofs + 0.5 + zshift * scale
9-
c = np.arange(img.w)[np.newaxis] + img.xofs - scale + 0.5
10-
if style == "ground":
11-
if cut_inside:
12-
raise NotImplementedError()
13-
else:
14-
alphamask = (
15-
(r * 2 - c <= 48 * scale)
16-
* (r * 2 + c <= 48 * scale)
17-
* (r * 2 + c >= -16 * scale)
18-
* (r * 2 - c >= -16 * scale)
19-
)
20-
elif style == "simple":
21-
if part == 0:
22-
alphamask = (r * 4 - c * 3 <= 128 * scale) * (c <= 0)
23-
if cut_inside:
24-
alphamask *= r * 2 - c >= 48 * scale
25-
elif not solid:
26-
alphamask *= r * 2 + c >= -16 * scale
27-
elif part == 1:
28-
alphamask = (r * 2 - c <= 64 * scale) * (c <= 0)
29-
if cut_inside:
30-
alphamask *= r * 2 - c >= 48 * scale
31-
elif not solid:
32-
alphamask *= r * 4 + c * 3 >= -32 * scale
33-
elif part == 2:
34-
alphamask = (r * 4 - c * 1 <= 96 * scale) * (c <= 0)
35-
if cut_inside:
36-
alphamask *= r * 2 - c >= 48 * scale
37-
elif not solid:
38-
alphamask *= r * 2 + c >= 0
39-
elif part == 3:
40-
alphamask = (r * 4 + c * 3 <= 128 * scale) * (c >= 0)
41-
if cut_inside:
42-
alphamask *= r * 2 + c >= 48 * scale
43-
elif not solid:
44-
alphamask *= r * 2 - c >= -16 * scale
45-
elif part == 4:
46-
alphamask = (r * 2 + c <= 64 * scale) * (c >= 0)
47-
if cut_inside:
48-
alphamask *= r * 2 + c >= 48 * scale
49-
elif not solid:
50-
alphamask *= r * 4 - c * 3 >= -32 * scale
51-
elif part == 5:
52-
alphamask = (r * 4 + c * 1 <= 96 * scale) * (c >= 0)
53-
if cut_inside:
54-
alphamask *= r * 2 + c >= 48 * scale
55-
elif not solid:
56-
alphamask *= r * 2 - c >= 0
57-
elif part == 6:
58-
if cut_inside:
59-
alphamask = (r * 2 + c <= -16 * scale) * (c <= 0)
60-
else:
61-
alphamask = 0
62-
elif part == 7:
63-
if cut_inside:
64-
alphamask = (r * 2 - c <= -16 * scale) * (c >= 0)
65-
else:
66-
alphamask = 0
67-
else:
68-
assert False, f"Unsupported part: {part}"
69-
elif style == "extended":
70-
if part == 0:
71-
alphamask = (r * 2 - c * 1 <= 48 * scale) * (r * 4 + c <= 96 * scale)
72-
if not solid:
73-
alphamask *= r * 4 + c >= 0 * scale
74-
alphamask *= r * 2 - c >= 0 * scale
75-
elif part == 1:
76-
alphamask = (r * 4 - c * 3 <= 128 * scale) * (r * 4 + c * 3 <= 128 * scale)
77-
if not solid:
78-
alphamask *= r * 4 + c >= 0 * scale
79-
alphamask *= r * 4 - c >= 0 * scale
80-
elif part == 2:
81-
alphamask = (r * 4 - c * 1 <= 96 * scale) * (r * 2 + c <= 48 * scale)
82-
if not solid:
83-
alphamask *= r * 2 + c >= 0 * scale
84-
alphamask *= r * 4 - c >= 0 * scale
85-
elif part == 3:
86-
alphamask = (r * 2 - c <= 48 * scale) * (r * 2 + c <= 48 * scale)
87-
if not solid:
88-
alphamask *= r * 4 + c >= 0 * scale
89-
alphamask *= r * 4 - c >= 0 * scale
90-
elif part == 4:
91-
alphamask = (r * 4 - c * 3 <= 128 * scale) * (r * 2 + c <= 64 * scale)
92-
if not solid:
93-
alphamask *= r * 2 + c >= -16 * scale
94-
alphamask *= r * 4 - c * 3 >= -32 * scale
95-
elif part == 5:
96-
alphamask = (r * 4 - c * 1 <= 96 * scale) * (r * 4 + c * 1 <= 96 * scale)
97-
if not solid:
98-
alphamask *= r * 4 + c * 3 >= -32 * scale
99-
alphamask *= r * 4 - c * 3 >= -32 * scale
100-
elif part == 6:
101-
alphamask = (r * 2 - c <= 48 * scale) * (r * 4 + c * 1 <= 96 * scale)
102-
if not solid:
103-
alphamask *= r * 2 + c >= -16 * scale
104-
alphamask *= r * 4 - c * 3 >= -32 * scale
105-
elif part == 7:
106-
alphamask = (r * 2 - c <= 64 * scale) * (r * 4 + c * 3 <= 128 * scale)
107-
if not solid:
108-
alphamask *= r * 4 + c * 3 >= -32 * scale
109-
alphamask *= r * 2 - c >= -16 * scale
110-
elif part == 8:
111-
alphamask = (r * 4 - c * 3 <= 128 * scale) * (r * 4 + c * 3 <= 128 * scale)
112-
if not solid:
113-
alphamask *= r * 2 + c >= -16 * scale
114-
alphamask *= r * 2 - c >= -16 * scale
115-
elif part == 9:
116-
alphamask = (r * 4 - c * 1 <= 96 * scale) * (r * 2 + c <= 48 * scale)
117-
if not solid:
118-
alphamask *= r * 4 + c * 3 >= -32 * scale
119-
alphamask *= r * 2 - c >= -16 * scale
7+
8+
def get_left_part(left_parts, r, c, solid, scale):
9+
left = np.ones_like(r, dtype=np.uint8) * ((c >= -32 * scale) * (c <= 0 * scale))
10+
11+
# Top limit - not applicable to solid parts
12+
# special case for None which indicates a quarter-base in simple mode
13+
if left_parts[2] is None:
14+
left *= r >= 8 * scale
15+
else:
16+
if not solid:
17+
if left_parts[2] == left_parts[3]:
18+
left *= r * 2 + c >= (-16 * left_parts[3]) * scale
19+
elif left_parts[2] == left_parts[3] + 1:
20+
left *= r * 4 + c >= (-16 * left_parts[3]) * 2 * scale
12021
else:
121-
assert False, f"Unsupported part: {part}"
122-
alphamask *= c >= -32 * scale
123-
alphamask *= c <= 32 * scale
124-
alpha = img.alpha * alphamask
22+
assert left_parts[2] == left_parts[3] - 1
23+
left *= r * 4 + c * 3 >= (-16 * left_parts[3]) * 2 * scale
24+
25+
if left_parts[0] is None:
26+
left *= r <= 8 * scale
27+
elif left_parts[0] == left_parts[1]:
28+
left *= r * 2 - c <= (64 - 16 * left_parts[0]) * scale
29+
elif left_parts[0] == left_parts[1] + 1:
30+
left *= r * 4 - c <= (64 - 16 * left_parts[0]) * 2 * scale
12531
else:
32+
assert left_parts[0] == left_parts[1] - 1
33+
left *= r * 4 - c * 3 <= (64 - 16 * left_parts[0]) * 2 * scale
34+
35+
return left.astype(np.uint8)
36+
37+
38+
def make_foundation_subimage(
39+
img: LayeredImage, scale, left_parts, right_parts, nw, ne, y_limit, cut_inside, zshift, solid
40+
) -> LayeredImage:
41+
r = np.arange(img.h)[:, np.newaxis] + img.yofs + 0.5 + zshift * scale
42+
c = np.arange(img.w)[np.newaxis] + img.xofs - scale + 0.5
43+
44+
alphamask = np.zeros((img.h, img.w), dtype=np.uint8)
45+
if left_parts is not None:
46+
alphamask = np.maximum(alphamask, get_left_part(left_parts, r, c, solid, scale))
47+
48+
if right_parts is not None:
49+
alphamask = np.maximum(alphamask, get_left_part(right_parts, r, -c, solid, scale))
50+
51+
if cut_inside:
52+
alphamask *= (
53+
1
54+
- (r * 2 + c >= -16 * scale)
55+
* (r * 2 - c >= -16 * scale)
56+
* (r * 2 + c < 48 * scale)
57+
* (r * 2 - c < 48 * scale)
58+
).astype(np.uint8)
59+
60+
if not solid:
61+
if nw:
62+
alphamask *= (1 - (c < 0) * (r * 2 + c < 0 * scale) * (r * 4 - c < 64 * scale)).astype(np.uint8)
63+
if ne:
64+
alphamask *= (1 - (c > 0) * (r * 2 - c < 0 * scale) * (r * 4 + c < 64 * scale)).astype(np.uint8)
65+
66+
alphamask *= (1 - (r * 4 - c > y_limit * 2 * scale) * (r * 4 + c > y_limit * 2 * scale)).astype(np.uint8)
67+
68+
if img.alpha is None:
12669
alpha = None
70+
assert img.mask is not None
71+
assert img.rgb is None
72+
mask = img.mask * alphamask
73+
else:
74+
alpha = img.alpha * alphamask
75+
assert img.rgb is not None
76+
mask = img.mask
12777

128-
return LayeredImage(xofs=img.xofs, yofs=img.yofs, w=img.w, h=img.h, rgb=img.rgb, alpha=alpha, mask=None)
78+
return LayeredImage(xofs=img.xofs, yofs=img.yofs, w=img.w, h=img.h, rgb=img.rgb, alpha=alpha, mask=mask)
12979

13080

131-
def make_foundation(solid: LayeredImage, ground: LayeredImage, scale, part, style, cut_inside, zshift) -> LayeredImage:
81+
def make_foundation(
82+
solid: LayeredImage, ground: LayeredImage, scale, left_parts, right_parts, nw, ne, y_limit, cut_inside, zshift
83+
) -> LayeredImage:
13284
if solid is not None:
133-
solid = make_foundation_subimage(solid, scale, part, style, cut_inside, zshift, True)
85+
solid = make_foundation_subimage(
86+
solid, scale, left_parts, right_parts, nw, ne, y_limit, cut_inside, zshift, True
87+
)
13488
if ground is not None:
135-
ground = make_foundation_subimage(ground, scale, part, style, cut_inside, zshift, False)
89+
ground = make_foundation_subimage(
90+
ground, scale, left_parts, right_parts, nw, ne, y_limit, cut_inside, zshift, False
91+
)
13692
if solid is not None and ground is not None:
13793
return ground.copy().blend_over(solid)
13894
return solid or ground

agrf/graphics/cv/foundation_test.py

Lines changed: 63 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,46 +12,93 @@ def _full_alpha_image(w=16, h=16, xofs=0, yofs=0):
1212

1313
def test_make_foundation_subimage_basic_left_side():
1414
base = _full_alpha_image()
15-
# part 0 targets the left side (c <= 0) when scale=4 and xofs=0
16-
out = make_foundation_subimage(base, scale=4, part=0, style="simple", cut_inside=False, zshift=0, solid=True)
15+
# part (6, 0) targets the left side (c <= 0) when scale=4 and xofs=0
16+
out = make_foundation_subimage(
17+
base,
18+
scale=4,
19+
left_parts=6,
20+
right_parts=None,
21+
nw=False,
22+
ne=False,
23+
y_limit=64,
24+
cut_inside=False,
25+
zshift=0,
26+
solid=True,
27+
)
1728
assert out.alpha is not None
1829
# Left-most column should be non-zero
1930
assert out.alpha[0, 0] > 0
2031
# A column clearly on the right should be zero
2132
assert out.alpha[0, 8] == 0
2233

2334

24-
def test_make_foundation_subimage_alpha_none_passthrough():
25-
# If input has no alpha, output keeps alpha as None
26-
rgb = np.zeros((8, 8, 3), dtype=np.uint8)
27-
base = LayeredImage(0, 0, 8, 8, rgb, None, None)
28-
out = make_foundation_subimage(base, scale=4, part=0, style="simple", cut_inside=False, zshift=0, solid=True)
29-
assert out.alpha is None
30-
31-
3235
def test_make_foundation_passthrough_single_input():
3336
base = _full_alpha_image()
3437
# Only solid provided
3538
expected_solid = make_foundation_subimage(
36-
base.copy(), scale=4, part=0, style="simple", cut_inside=False, zshift=0, solid=True
39+
base.copy(),
40+
scale=4,
41+
left_parts=6,
42+
right_parts=None,
43+
nw=False,
44+
ne=False,
45+
y_limit=64,
46+
cut_inside=False,
47+
zshift=0,
48+
solid=True,
49+
)
50+
out_solid = make_foundation(
51+
base, None, scale=4, left_parts=6, right_parts=None, nw=False, ne=False, y_limit=64, cut_inside=False, zshift=0
3752
)
38-
out_solid = make_foundation(base, None, scale=4, part=0, style="simple", cut_inside=False, zshift=0)
3953
assert np.array_equal(out_solid.alpha, expected_solid.alpha)
4054

4155
# Only ground provided
4256
expected_ground = make_foundation_subimage(
43-
base.copy(), scale=4, part=0, style="simple", cut_inside=False, zshift=0, solid=False
57+
base.copy(),
58+
scale=4,
59+
left_parts=6,
60+
right_parts=None,
61+
nw=False,
62+
ne=False,
63+
y_limit=64,
64+
cut_inside=False,
65+
zshift=0,
66+
solid=False,
67+
)
68+
out_ground = make_foundation(
69+
None, base, scale=4, left_parts=6, right_parts=None, nw=False, ne=False, y_limit=64, cut_inside=False, zshift=0
4470
)
45-
out_ground = make_foundation(None, base, scale=4, part=0, style="simple", cut_inside=False, zshift=0)
4671
assert np.array_equal(out_ground.alpha, expected_ground.alpha)
4772

4873

4974
def test_make_foundation_subimage_zshift_reduces_coverage_for_part0():
5075
# Use a large y offset to sit near the inequality boundary so zshift has an effect
5176
base = _full_alpha_image()
5277
base.yofs = 120
53-
out0 = make_foundation_subimage(base, scale=4, part=0, style="simple", cut_inside=False, zshift=0, solid=True)
54-
out1 = make_foundation_subimage(base, scale=4, part=0, style="simple", cut_inside=False, zshift=2, solid=True)
78+
out0 = make_foundation_subimage(
79+
base,
80+
scale=4,
81+
left_parts=6,
82+
right_parts=None,
83+
nw=False,
84+
ne=False,
85+
y_limit=64,
86+
cut_inside=False,
87+
zshift=0,
88+
solid=True,
89+
)
90+
out1 = make_foundation_subimage(
91+
base,
92+
scale=4,
93+
left_parts=6,
94+
right_parts=None,
95+
nw=False,
96+
ne=False,
97+
y_limit=64,
98+
cut_inside=False,
99+
zshift=2,
100+
solid=True,
101+
)
55102
sum0 = int(out0.alpha.sum())
56103
sum1 = int(out1.alpha.sum())
57104
assert sum0 > 0

0 commit comments

Comments
 (0)