spriteforge/tests/test_pixel.py

143 lines
5.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Примитивы пиксель-арта: границы, детерминизм, порядок по глубине."""
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"]