143 lines
5.5 KiB
Python
143 lines
5.5 KiB
Python
|
|
"""Примитивы пиксель-арта: границы, детерминизм, порядок по глубине."""
|
|||
|
|
import numpy as np
|
|||
|
|
import pytest
|
|||
|
|
|
|||
|
|
from spriteforge import pixel
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_colors_are_parsed_and_validated():
|
|||
|
|
assert list(pixel.to_rgba("#102030")) == [16, 32, 48, 255]
|
|||
|
|
assert list(pixel.to_rgba("#10203040")) == [16, 32, 48, 64]
|
|||
|
|
assert list(pixel.to_rgba((1, 2, 3))) == [1, 2, 3, 255]
|
|||
|
|
for bad in ("102030", "#1020", "#GGGGGG"):
|
|||
|
|
with pytest.raises(ValueError):
|
|||
|
|
pixel.to_rgba(bad)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_canvas_starts_transparent_and_fills():
|
|||
|
|
image = pixel.canvas(5, 3)
|
|||
|
|
assert image.shape == (3, 5, 4) and image.dtype == np.uint8 and not image.any()
|
|||
|
|
pixel.fill(image, "#204060")
|
|||
|
|
assert (image[..., 3] == 255).all()
|
|||
|
|
with pytest.raises(ValueError):
|
|||
|
|
pixel.canvas(0, 4)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_fill_blends_over_the_whole_canvas():
|
|||
|
|
image = pixel.canvas(4, 4, "#000000")
|
|||
|
|
pixel.fill(image, "#FFFFFF80", blend=True)
|
|||
|
|
assert image[0, 0, 0] == 128 and image[0, 0, 3] == 255
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_set_pixel_ignores_everything_outside_the_canvas():
|
|||
|
|
image = pixel.canvas(4, 4)
|
|||
|
|
for x, y in ((-1, 0), (0, -1), (4, 0), (0, 4)):
|
|||
|
|
pixel.set_pixel(image, x, y, "#FFFFFF")
|
|||
|
|
assert not image.any()
|
|||
|
|
pixel.set_pixel(image, 3, 3, "#FFFFFF")
|
|||
|
|
assert list(image[3, 3]) == [255, 255, 255, 255]
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_lines_stay_inside_the_canvas_and_thicken():
|
|||
|
|
image = pixel.canvas(9, 9)
|
|||
|
|
pixel.draw_line(image, -5, 4, 20, 4, "#FFFFFF")
|
|||
|
|
assert (image[4, :, 3] == 255).all() and image[3, :, 3].sum() == 0
|
|||
|
|
thick = pixel.canvas(9, 9)
|
|||
|
|
pixel.draw_line(thick, 4, 1, 4, 7, "#FFFFFF", thickness=3)
|
|||
|
|
assert np.count_nonzero(thick[..., 3]) > np.count_nonzero(image[..., 3])
|
|||
|
|
assert pixel.line_points(0, 0, 2, 2) == ((0, 0), (1, 1), (2, 2))
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_rectangles_and_ellipses_support_outlines():
|
|||
|
|
image = pixel.canvas(10, 10)
|
|||
|
|
pixel.draw_rect(image, 2, 2, 7, 7, "#FFFFFF", filled=False)
|
|||
|
|
assert image[2, 2, 3] == 255 and image[4, 4, 3] == 0
|
|||
|
|
pixel.draw_rect(image, -3, -3, 1, 1, "#FF0000")
|
|||
|
|
assert image[0, 0, 3] == 255
|
|||
|
|
ellipse = pixel.canvas(11, 11)
|
|||
|
|
pixel.draw_ellipse(ellipse, 5, 5, 4, 3, "#FFFFFF")
|
|||
|
|
assert ellipse[5, 1, 3] == 255 and ellipse[1, 5, 3] == 0
|
|||
|
|
hollow = pixel.canvas(11, 11)
|
|||
|
|
pixel.draw_ellipse(hollow, 5, 5, 4, 3, "#FFFFFF", filled=False, thickness=1)
|
|||
|
|
assert hollow[5, 5, 3] == 0 and hollow[5, 1, 3] == 255
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_capsule_covers_only_its_own_radius():
|
|||
|
|
image = pixel.canvas(12, 12)
|
|||
|
|
pixel.draw_capsule(image, 3, 6, 8, 6, 3.0, "#FFFFFF")
|
|||
|
|
assert image[6, 3, 3] == 255 and image[6, 8, 3] == 255
|
|||
|
|
assert image[6, 11, 3] == 0 and image[0, 3, 3] == 0
|
|||
|
|
mask = pixel.capsule_mask(12, 12, 3, 6, 3, 6, 5.0)
|
|||
|
|
assert mask[6, 3] and not mask[6, 9]
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_outline_marks_only_the_silhouette_edge():
|
|||
|
|
image = pixel.canvas(9, 9)
|
|||
|
|
pixel.draw_rect(image, 2, 2, 6, 6, "#FFFFFF")
|
|||
|
|
pixel.outline(image, "#000000")
|
|||
|
|
assert list(image[2, 2, :3]) == [0, 0, 0]
|
|||
|
|
assert list(image[4, 4, :3]) == [255, 255, 255]
|
|||
|
|
# Силуэт, упирающийся в край холста, всё равно получает кромку с этой стороны.
|
|||
|
|
touching = pixel.edge_mask(pixel.canvas(4, 4, "#FFFFFF"))
|
|||
|
|
assert touching[0].all() and touching[:, 0].all() and touching[-1].all()
|
|||
|
|
assert not touching[1:3, 1:3].any()
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_shading_ramp_and_depth_factor():
|
|||
|
|
shadow, base, highlight = pixel.ramp("#808080")
|
|||
|
|
assert int(shadow[0]) < int(base[0]) < int(highlight[0])
|
|||
|
|
assert int(pixel.shade("#FFFFFF", 2.0)[0]) == 255 and int(pixel.shade("#804020", 1.0)[3]) == 255
|
|||
|
|
assert pixel.depth_factor(0.0) < pixel.depth_factor(0.5) < pixel.depth_factor(1.0) == 1.0
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_far_limbs_are_drawn_first_and_darker():
|
|||
|
|
near = pixel.Limb(2, 2, 2, 9, 3.0, "#A0A0A0", depth=1.0)
|
|||
|
|
far = pixel.Limb(6, 2, 6, 9, 3.0, "#A0A0A0", depth=0.0)
|
|||
|
|
assert pixel.sort_by_depth((near, far)) == (far, near)
|
|||
|
|
image = pixel.canvas(12, 12)
|
|||
|
|
pixel.draw_limbs(image, (near, far))
|
|||
|
|
assert int(image[5, 6, 0]) < int(image[5, 2, 0])
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_overlapping_limbs_let_the_near_one_win():
|
|||
|
|
image = pixel.canvas(12, 12)
|
|||
|
|
far = pixel.Limb(2, 6, 9, 6, 3.0, "#FFFFFF", depth=0.0)
|
|||
|
|
near = pixel.Limb(2, 6, 9, 6, 3.0, "#FFFFFF", depth=1.0)
|
|||
|
|
pixel.draw_limbs(image, (far, near))
|
|||
|
|
assert int(image[6, 5, 0]) == 255
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_rim_light_touches_only_the_lit_side():
|
|||
|
|
image = pixel.canvas(16, 16)
|
|||
|
|
pixel.draw_ellipse(image, 8, 8, 5, 5, "#404040")
|
|||
|
|
lit = image.copy()
|
|||
|
|
pixel.rim_light(lit, (-1.0, 0.0), "#FFFFFF", strength=1.0)
|
|||
|
|
left = int(lit[8, 3, 0]) - int(image[8, 3, 0])
|
|||
|
|
right = int(lit[8, 13, 0]) - int(image[8, 13, 0])
|
|||
|
|
assert left > 0 and right == 0
|
|||
|
|
assert (lit[..., 3] == image[..., 3]).all()
|
|||
|
|
with pytest.raises(ValueError):
|
|||
|
|
pixel.rim_light(lit, (0.0, 0.0))
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_noise_uses_only_the_supplied_generator():
|
|||
|
|
def painted():
|
|||
|
|
image = pixel.canvas(8, 8)
|
|||
|
|
pixel.draw_rect(image, 1, 1, 6, 6, "#606060")
|
|||
|
|
return image
|
|||
|
|
first, second, other = painted(), painted(), painted()
|
|||
|
|
pixel.add_noise(first, np.random.default_rng(4))
|
|||
|
|
pixel.add_noise(second, np.random.default_rng(4))
|
|||
|
|
pixel.add_noise(other, np.random.default_rng(5))
|
|||
|
|
assert np.array_equal(first, second) and not np.array_equal(first, other)
|
|||
|
|
assert not first[0, 0].any()
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_flip_keeps_the_pivot_on_the_same_pixel():
|
|||
|
|
image = pixel.canvas(6, 3)
|
|||
|
|
pixel.set_pixel(image, 1, 1, "#FFFFFF")
|
|||
|
|
flipped = pixel.flip_horizontal(image)
|
|||
|
|
assert flipped[1, 4, 3] == 255
|
|||
|
|
assert pixel.flip_pivot_x(1, 6) == 4
|
|||
|
|
assert flipped.flags["C_CONTIGUOUS"]
|