Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draw rect tests + fixes #2195

Merged
merged 13 commits into from
Jul 3, 2024
4 changes: 0 additions & 4 deletions arcade/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,6 @@ def configure_logging(level: Optional[int] = None):
from .draw import draw_triangle_outline
from .draw import draw_lbwh_rectangle_filled
from .draw import draw_lbwh_rectangle_outline
from .draw import draw_rect_filled_kwargs
from .draw import draw_rect_outline_kwargs
from .screenshot import get_image
from .screenshot import get_pixel

Expand Down Expand Up @@ -343,8 +341,6 @@ def configure_logging(level: Optional[int] = None):
"draw_triangle_outline",
"draw_lbwh_rectangle_filled",
"draw_lbwh_rectangle_outline",
"draw_rect_outline_kwargs",
"draw_rect_filled_kwargs",
"enable_timings",
"exit",
"finish_render",
Expand Down
4 changes: 0 additions & 4 deletions arcade/draw/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@
draw_lrbt_rectangle_filled,
draw_lbwh_rectangle_filled,
draw_rect_filled,
draw_rect_outline_kwargs,
draw_rect_filled_kwargs,
draw_texture_rect,
draw_sprite,
draw_sprite_rect,
Expand Down Expand Up @@ -66,8 +64,6 @@
"draw_lrbt_rectangle_filled",
"draw_lbwh_rectangle_filled",
"draw_rect_filled",
"draw_rect_outline_kwargs",
"draw_rect_filled_kwargs",
"draw_texture_rect",
"draw_sprite",
"draw_sprite_rect",
Expand Down
27 changes: 16 additions & 11 deletions arcade/draw/rect.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from arcade.math import rotate_point
from arcade.sprite import BasicSprite
from arcade.texture import Texture
from arcade.types import LBWH, LRBT, RGBA255, XYWH, AsFloat, Color, PointList, Rect
from arcade.types import LBWH, LRBT, RGBA255, XYWH, Color, PointList, Rect
from arcade.window_commands import get_window

from .helpers import _generic_draw_line_strip
Expand All @@ -32,8 +32,11 @@ def draw_texture_rect(
:param alpha: Transparency of image. 0.0 is fully transparent, 1.0 (default) is visible.
"""
ctx = get_window().ctx

if blend:
ctx.enable(ctx.BLEND)
else:
ctx.disable(ctx.BLEND)

atlas = ctx.default_atlas

Expand Down Expand Up @@ -262,15 +265,17 @@ def draw_rect_filled(rect: Rect, color: RGBA255, tilt_angle: float = 0) -> None:
ctx.disable(ctx.BLEND)


def draw_rect_outline_kwargs(
color: RGBA255 = WHITE, border_width: int = 1, tilt_angle: float = 0, **kwargs: AsFloat
) -> None:
rect = Rect.from_kwargs(**kwargs)
draw_rect_outline(rect, color, border_width, tilt_angle)
# These might be "oddly specific" and also needs docstrings. Disabling or 3.0.0

# def draw_rect_outline_kwargs(
# color: RGBA255 = WHITE, border_width: int = 1, tilt_angle: float = 0, **kwargs: AsFloat
# ) -> None:
# rect = Rect.from_kwargs(**kwargs)
# draw_rect_outline(rect, color, border_width, tilt_angle)

def draw_rect_filled_kwargs(
color: RGBA255 = WHITE, tilt_angle: float = 0, **kwargs: AsFloat
) -> None:
rect = Rect.from_kwargs(**kwargs)
draw_rect_filled(rect, color, tilt_angle)

# def draw_rect_filled_kwargs(
# color: RGBA255 = WHITE, tilt_angle: float = 0, **kwargs: AsFloat
# ) -> None:
# rect = Rect.from_kwargs(**kwargs)
# draw_rect_filled(rect, color, tilt_angle)
2 changes: 2 additions & 0 deletions arcade/sprite_list/sprite_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -1048,6 +1048,8 @@ def draw(
self.ctx.blend_func = blend_function
else:
self.ctx.blend_func = self.ctx.BLEND_DEFAULT
else:
self.ctx.disable(self.ctx.BLEND)

# Workarounds for Optional[TextureAtlas] + slow . lookup speed
atlas: DefaultTextureAtlas = self.atlas # type: ignore
Expand Down
95 changes: 93 additions & 2 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
from __future__ import annotations

import gc
import os
from contextlib import contextmanager
from pathlib import Path

if os.environ.get("ARCADE_PYTEST_USE_RUST"):
import arcade_accelerate # pyright: ignore [reportMissingImports]
arcade_accelerate.bootstrap()

import pytest
import PIL.Image
from pyglet.math import Mat4

import arcade
from arcade import Rect, LBWH
from arcade import gl
# from arcade.texture import default_texture_cache

Expand All @@ -20,6 +21,7 @@
arcade.resources.add_resource_handle("fixtures", FIXTURE_ROOT)
REAL_WINDOW_CLASS = arcade.Window
WINDOW = None
OFFSCREEN = None


def create_window(width=800, height=600, caption="Testing", **kwargs):
Expand Down Expand Up @@ -287,3 +289,92 @@ def open_window(*args, **kwargs):
yield None
arcade.Window = _window
arcade.open_window = _open_window


# --- Fixtures for offscreen rendering
class Offscreen:

def __init__(self):
self.ctx = WINDOW.ctx
self.texture = self.ctx.texture((1280, 720), components=4)
self.fbo = self.ctx.framebuffer(color_attachments=[self.texture])

def use(self):
"""Use the offscreen buffer"""
self.fbo.use()
WINDOW.projection = Mat4.orthogonal_projection(0, 1280, 0, 720, -100, 100)

def clear(self):
"""Clear the offscreen buffer"""
self.fbo.clear()

def get_image(self) -> PIL.Image.Image:
"""Get the offscreen buffer as an image"""
region = LBWH(0, 0, 1280, 720)
return self.read_region_image(region)

def read_pixel(self, x, y, components=3) -> tuple[int, int, int, int] | tuple[int, int, int]:
"""Read a single RGBA pixel from the offscreen buffer"""
data = self.fbo.read(components=4, viewport=(x, y, 1, 1))
return (
int.from_bytes(data[0:4], "little"),
int.from_bytes(data[4:8], "little"),
int.from_bytes(data[8:12], "little"),
int.from_bytes(data[12:16], "little"),
)

def read_region(self, rect: Rect) -> list[tuple[int, int, int, int]]:
"""Read a region of RGBA pixels from the offscreen buffer"""
data = self.fbo.read(components=4, viewport=(rect.left, rect.bottom, rect.width, rect.height))
return [
(
int.from_bytes(data[i : i + 4], "little"),
int.from_bytes(data[i + 4 : i + 8], "little"),
int.from_bytes(data[i + 8 : i + 12], "little"),
int.from_bytes(data[i + 12 : i + 16], "little"),
)
for i in range(0, len(data), 16)
]

def read_region_bytes(self, rect: Rect, components=3) -> bytes:
"""Read a region of RGBA pixels from the offscreen buffer as bytes"""
return self.fbo.read(
components=components,
viewport=(rect.left, rect.bottom, rect.width, rect.height),
)

def read_region_image(self, rect: Rect, components=3) -> PIL.Image.Image:
im = PIL.Image.frombytes(
"RGBA" if components == 4 else "RGB",
(int(rect.width), int(rect.height)),
self.read_region_bytes(rect, components=components),
)
return im.transpose(PIL.Image.Transpose.FLIP_TOP_BOTTOM)

def assert_images_almost_equal(self, image1: PIL.Image.Image, image2: PIL.Image.Image, abs=2):
"""Assert that two images are almost equal"""
assert image1.size == image2.size, f"{image1.size} != {image2.size}"
assert image1.mode == image2.mode, f"{image1.mode} != {image2.mode}"
assert image1.tobytes() == pytest.approx(image2.tobytes(), abs=abs)


@pytest.fixture(scope="function")
def offscreen():
"""
Offscreen rendering tools.

A global offscreen 720p target that can be used for testing
"""
global OFFSCREEN

window = create_window()
arcade.set_window(window)
prepare_window(window)

if OFFSCREEN is None:
OFFSCREEN = Offscreen()

OFFSCREEN.clear()
OFFSCREEN.use()
yield OFFSCREEN
window.use()
Original file line number Diff line number Diff line change
@@ -1,15 +1,7 @@
import arcade

SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600


def test_drawing_dims(window):
window.background_color = arcade.color.WHITE

# Start the render process. This must be done before any drawing commands.
arcade.start_render()

radius = 50
width = radius * 2
x = 200
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ def test_draw_primitives(window):
"""
Render the screen.
"""
window.background_color = (255, 255, 255, 255) # arcade.color.WHITE)
window.background_color = arcade.color.WHITE
window.clear()

# Draw a grid
Expand Down Expand Up @@ -146,4 +146,4 @@ def test_draw_primitives(window):
assert color == (255, 255, 255)

# Run the get image. Ideally we'd test the output
arcade.get_image()
# arcade.get_image()
47 changes: 47 additions & 0 deletions tests/unit/draw/test_rect.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from PIL import Image, ImageDraw

import arcade
from arcade import LBWH

IMAGE = Image.new("RGBA", (2, 2), (0, 0, 0, 0))
draw = ImageDraw.Draw(IMAGE)
draw.point((0, 0), fill=(255, 0, 0, 255)) # upper left
draw.point((1, 0), fill=(0, 255, 0, 255)) # upper right
draw.point((0, 1), fill=(0, 0, 255, 255)) # lower left
draw.point((1, 1), fill=(255, 255, 255, 255)) # lower right

TEXTURE = arcade.Texture(IMAGE)


def test_draw_texture_rect(window, offscreen):
"""Draw a texture rect and compare it to the expected image."""
region = LBWH(0, 0, *TEXTURE.size)
arcade.draw_texture_rect(TEXTURE, region, blend=False, pixelated=True)

screen_image = offscreen.read_region_image(region, components=3)
expected_image = TEXTURE.image.convert("RGB")
offscreen.assert_images_almost_equal(screen_image, expected_image)


def test_draw_sprite(offscreen):
"""Draw a sprite and compare it to the expected image."""
sprite = arcade.Sprite(TEXTURE, center_x=1.0, center_y=1.0)
region = LBWH(0, 0, *TEXTURE.size)

arcade.draw_sprite(sprite, blend=False, pixelated=True)

screen_image = offscreen.read_region_image(region, components=3)
expected_image = TEXTURE.image.convert("RGB")
offscreen.assert_images_almost_equal(screen_image, expected_image, abs=1)


def test_draw_sprite_rect(offscreen):
"""Draw a sprite and compare it to the expected image."""
sprite = arcade.Sprite(TEXTURE, center_x=64, center_y=64)
region = LBWH(0, 0, *TEXTURE.size)

arcade.draw_sprite_rect(sprite, region, blend=False, pixelated=True)

screen_image = offscreen.read_region_image(region, components=3)
expected_image = TEXTURE.image.convert("RGB")
offscreen.assert_images_almost_equal(screen_image, expected_image, abs=1)
Loading