from __future__ import annotations
import array
from arcade import gl
from arcade.color import WHITE
from arcade.math import rotate_point
from arcade.sprite import BasicSprite
from arcade.texture import Texture
from arcade.texture_atlas.base import TextureAtlasBase
from arcade.types import LBWH, LRBT, XYWH, Color, Point2List, Rect, RGBOrA255
from arcade.window_commands import get_window
from .helpers import _generic_draw_line_strip
[docs]
def draw_texture_rect(
texture: Texture,
rect: Rect,
*,
color: Color = WHITE,
angle=0.0,
blend=True,
alpha=255,
pixelated=False,
atlas: TextureAtlasBase | None = None,
) -> None:
"""
Draw a texture on a rectangle.
Args:
texture:
The texture to draw.
rect:
Rectangle to draw the texture on.
color:
Color multiplier for the texture. Defaults to white.
angle:
Rotation of the texture in degrees. Defaults to zero.
blend:
If True, enable alpha blending. Defaults to True.
alpha:
Transparency of image. 0 is fully transparent, 255 (default) is fully opaque.
atlas:
The texture atlas the texture resides in.
if not supplied the default texture atlas is used
"""
ctx = get_window().ctx
# Clamp alpha to 0-255
alpha_normalized = max(0, min(255, alpha)) / 255.0
if blend:
ctx.enable(ctx.BLEND)
else:
ctx.disable(ctx.BLEND)
atlas = atlas or ctx.default_atlas
program = ctx.sprite_program_single
texture_id, _ = atlas.add(texture)
if pixelated:
atlas.texture.filter = gl.NEAREST, gl.NEAREST
program.set_uniform_safe("uv_offset_bias", 0.0)
else:
atlas.texture.filter = gl.LINEAR, gl.LINEAR
program.set_uniform_safe("uv_offset_bias", 1.0)
atlas.texture.use(unit=0)
atlas.use_uv_texture(unit=1)
geometry = ctx.geometry_empty
program["pos"] = rect.x, rect.y, 0
program["color"] = color.normalized
program["size"] = rect.width, rect.height
program["angle"] = angle
program["texture_id"] = float(texture_id)
program["spritelist_color"] = 1.0, 1.0, 1.0, alpha_normalized
geometry.render(program, mode=gl.POINTS, vertices=1)
if blend:
ctx.disable(ctx.BLEND)
[docs]
def draw_sprite(
sprite: BasicSprite,
*,
blend: bool = True,
alpha=255,
pixelated=False,
atlas: TextureAtlasBase | None = None,
) -> None:
"""
Draw a sprite.
Args:
sprite:
The sprite to draw.
blend:
Draw the sprite with or without alpha blending
alpha:
Fade the sprite from completely transparent to opaque (range: 0 to 255)
pixelated:
If true the sprite will be render in pixelated style. Otherwise smooth/linear
atlas:
The texture atlas the texture resides in.
if not supplied the default texture atlas is used
"""
draw_texture_rect(
sprite.texture,
rect=XYWH(sprite.center_x, sprite.center_y, sprite.width, sprite.height),
color=sprite.color,
angle=sprite._angle,
blend=blend,
alpha=alpha,
pixelated=pixelated,
atlas=atlas,
)
[docs]
def draw_sprite_rect(
sprite: BasicSprite,
rect: Rect,
*,
blend: bool = True,
alpha=255,
pixelated=False,
atlas: TextureAtlasBase | None = None,
) -> None:
"""Draw a sprite.
Args:
sprite:
The sprite to draw.
rect:
The location and size of the sprite
blend:
Draw the sprite with or without alpha blending
alpha:
Fade the sprite from completely transparent to opaque (range: 0 to 255)
pixelated:
If true the sprite will be render in pixelated style.
Otherwise smooth/linear
atlas:
The texture atlas the texture resides in.
if not supplied the default texture atlas is used
"""
draw_texture_rect(
sprite.texture,
rect=rect,
color=sprite.color,
angle=sprite._angle,
blend=blend,
alpha=alpha,
pixelated=pixelated,
atlas=atlas,
)
[docs]
def draw_lrbt_rectangle_outline(
left: float,
right: float,
bottom: float,
top: float,
color: RGBOrA255,
border_width: float = 1,
) -> None:
"""
Draw a rectangle by specifying left, right, bottom and top edges.
Args:
left:
The x coordinate of the left edge of the rectangle.
right:
The x coordinate of the right edge of the rectangle.
bottom:
The y coordinate of the rectangle bottom.
top:
The y coordinate of the top of the rectangle.
color:
The outline color as an RGBA :py:class:`tuple`, RGB
:py:class:`tuple`, or a :py:class:`.Color` instance.
border_width:
The width of the border in pixels. Defaults to one.
Raises:
ValueError: Raised if left > right or top < bottom.
"""
if left > right:
raise ValueError("Left coordinate must be less than or equal to " "the right coordinate")
if bottom > top:
raise ValueError("Bottom coordinate must be less than or equal to " "the top coordinate")
draw_rect_outline(LRBT(left, right, bottom, top), color, border_width)
[docs]
def draw_lbwh_rectangle_outline(
left: float,
bottom: float,
width: float,
height: float,
color: RGBOrA255,
border_width: float = 1,
) -> None:
"""
Draw a rectangle extending from bottom left to top right
Args:
left:
The x coordinate of the left edge of the rectangle.
bottom:
The y coordinate of the bottom of the rectangle.
width:
The width of the rectangle.
height:
The height of the rectangle.
color:
The outline color as an RGBA :py:class:`tuple`, RGB
:py:class:`tuple`, or a :py:class:`.Color` instance.
border_width:
The width of the border in pixels. Defaults to one.
"""
draw_rect_outline(LBWH(left, bottom, width, height), color, border_width)
[docs]
def draw_lrbt_rectangle_filled(
left: float, right: float, bottom: float, top: float, color: RGBOrA255
) -> None:
"""
Draw a rectangle by specifying left, right, bottom and top edges.
Args:
left:
The x coordinate of the left edge of the rectangle.
right:
The x coordinate of the right edge of the rectangle.
bottom:
The y coordinate of the rectangle bottom.
top:
The y coordinate of the top of the rectangle.
color:
The fill color as an RGBA :py:class:`tuple`,
RGB :py:class:`tuple`, or a :py:class:`.Color` instance.
Raises:
ValueError: Raised if left > right or top < bottom.
"""
if left > right:
raise ValueError(
f"Left coordinate {left} must be less than or equal to the right coordinate {right}"
)
if bottom > top:
raise ValueError(
f"Bottom coordinate {bottom} must be less than or equal to the top coordinate {top}"
)
draw_rect_filled(LRBT(left, right, bottom, top), color)
[docs]
def draw_lbwh_rectangle_filled(
left: float, bottom: float, width: float, height: float, color: RGBOrA255
) -> None:
"""
Draw a filled rectangle extending from bottom left to top right
Args:
left:
The x coordinate of the left edge of the rectangle.
bottom:
The y coordinate of the bottom of the rectangle.
width:
The width of the rectangle.
height:
The height of the rectangle.
color:
The fill color as an RGBA :py:class:`tuple`, RGB
:py:class:`tuple`, :py:class:`.Color` instance
"""
draw_rect_filled(LBWH(left, bottom, width, height), color)
[docs]
def draw_rect_outline(
rect: Rect, color: RGBOrA255, border_width: float = 1, tilt_angle: float = 0
) -> None:
"""
Draw a rectangle outline.
Args:
rect:
The rectangle to draw. a :py:class:`~arcade.Rect` instance.
color:
The outline color as an RGBA :py:class:`tuple`,
RGB :py:class:`tuple`, or :py:class:`.Color` instance.
border_width:
width of the lines, in pixels.
tilt_angle:
rotation of the rectangle. Defaults to zero (clockwise).
"""
HALF_BORDER = border_width / 2
# Extremely unrolled code below
# fmt: off
left = rect.left
right = rect.right
bottom = rect.bottom
top = rect.top
x = rect.x
y = rect.y
# o = outer, i = inner
#
# o_left, o_top o_right, o_top
# +----------------------------------------+
# | i_left, i_top i_right, i_top |
# | +----------------------------------+ |
# | | | |
# | | | |
# | | | |
# | +----------------------------------+ |
# | i_left, i_bottom i_right , i_bottom |
# +----------------------------------------+
# o_left, o_bottom o_right, o_bottom
i_left = left + HALF_BORDER
i_bottom = bottom + HALF_BORDER
i_right = right - HALF_BORDER
i_top = top - HALF_BORDER
o_left = left - HALF_BORDER
o_bottom = bottom - HALF_BORDER
o_right = right + HALF_BORDER
o_top = top + HALF_BORDER
# Declared separately because the code below seems to break mypy
point_list: Point2List
# This is intentionally unrolled to minimize repacking tuples
if tilt_angle == 0:
point_list = (
(o_left , o_top ),
(i_left , i_top ),
(o_right , o_top ),
(i_right , i_top ),
(o_right , o_bottom),
(i_right , i_bottom),
(o_left , o_bottom),
(i_left , i_bottom),
(o_left , o_top ),
(i_left , i_top )
)
else:
point_list = (
rotate_point(o_left , o_top , x, y, tilt_angle),
rotate_point(i_left , i_top , x, y, tilt_angle),
rotate_point(o_right , o_top , x, y, tilt_angle),
rotate_point(i_right , i_top , x, y, tilt_angle),
rotate_point(o_right , o_bottom, x, y, tilt_angle),
rotate_point(i_right , i_bottom, x, y, tilt_angle),
rotate_point(o_left , o_bottom, x, y, tilt_angle),
rotate_point(i_left , i_bottom, x, y, tilt_angle),
rotate_point(o_left , o_top , x, y, tilt_angle),
rotate_point(i_left , i_top , x, y, tilt_angle)
)
# fmt: on
_generic_draw_line_strip(point_list, color, gl.TRIANGLE_STRIP)
[docs]
def draw_rect_filled(rect: Rect, color: RGBOrA255, tilt_angle: float = 0) -> None:
"""
Draw a filled-in rectangle.
Args:
rect:
The rectangle to draw. a :py:class:`~arcade.Rect` instance.
color:
The fill color as an RGBA :py:class:`tuple`,
RGB :py:class:`tuple`, or :py:class:`.Color` instance.
tilt_angle:
rotation of the rectangle (clockwise). Defaults to zero.
"""
# Fail if we don't have a window, context, or right GL abstractions
window = get_window()
ctx = window.ctx
program = ctx.shape_rectangle_filled_unbuffered_program # type: ignore
geometry = ctx.shape_rectangle_filled_unbuffered_geometry
buffer = ctx.shape_rectangle_filled_unbuffered_buffer # type: ignore
# Validate & normalize to a pass the shader an RGBA float uniform
color_normalized = Color.from_iterable(color).normalized
ctx.enable(ctx.BLEND)
# Pass data to the shader
program["color"] = color_normalized
program["shape"] = rect.width, rect.height, tilt_angle
buffer.orphan()
buffer.write(data=array.array("f", (rect.x, rect.y)))
geometry.render(program, mode=ctx.POINTS, vertices=1)
ctx.disable(ctx.BLEND)
# These might be "oddly specific" and also needs docstrings. Disabling or 3.0.0
# def draw_rect_outline_kwargs(
# color: RGBAOrA255 = 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: RGBAOrA255 = WHITE, tilt_angle: float = 0, **kwargs: AsFloat
# ) -> None:
# rect = Rect.from_kwargs(**kwargs)
# draw_rect_filled(rect, color, tilt_angle)