160 lines
5.6 KiB
Python
160 lines
5.6 KiB
Python
"""Drawing routines for software surfaces."""
|
|
import ctypes
|
|
from .compat import isiterable, UnsupportedError
|
|
from .array import to_ctypes
|
|
from .color import convert_to_color
|
|
from .. import surface, pixels, rect
|
|
from .algorithms import clipline
|
|
from .sprite import SoftwareSprite
|
|
|
|
__all__ = ["prepare_color", "fill", "line"]
|
|
|
|
|
|
def _get_target_surface(target):
|
|
"""Gets the SDL_surface from the passed target."""
|
|
if isinstance(target, surface.SDL_Surface):
|
|
rtarget = target
|
|
elif isinstance(target, SoftwareSprite):
|
|
rtarget = target.surface
|
|
else:
|
|
raise TypeError("unsupported target type")
|
|
return rtarget
|
|
|
|
|
|
def prepare_color(color, target):
|
|
"""Prepares the passed color for the passed target."""
|
|
color = convert_to_color(color)
|
|
pformat = None
|
|
# Software surfaces
|
|
if isinstance(target, pixels.SDL_PixelFormat):
|
|
pformat = target
|
|
elif isinstance(target, surface.SDL_Surface):
|
|
pformat = target.format.contents
|
|
elif isinstance(target, SoftwareSprite):
|
|
pformat = target.surface.format.contents
|
|
if pformat is None:
|
|
raise TypeError("unsupported target type")
|
|
if pformat.Amask != 0:
|
|
# Target has an alpha mask
|
|
return pixels.SDL_MapRGBA(pformat, color.r, color.g, color.b, color.a)
|
|
return pixels.SDL_MapRGB(pformat, color.r, color.g, color.b)
|
|
|
|
|
|
def fill(target, color, area=None):
|
|
"""Fills a certain rectangular area on the passed target with a color.
|
|
|
|
If no area is provided, the entire target will be filled with
|
|
the passed color. If an iterable item is provided as area (such as a list
|
|
or tuple), it will be first checked, if the item denotes a single
|
|
rectangular area (4 integer values) before assuming it to be a sequence
|
|
of rectangular areas.
|
|
"""
|
|
color = prepare_color(color, target)
|
|
rtarget = _get_target_surface(target)
|
|
|
|
varea = None
|
|
if area is not None and isiterable(area):
|
|
# can be either a single rect or a list of rects)
|
|
if len(area) == 4:
|
|
# is it a rect?
|
|
try:
|
|
varea = rect.SDL_Rect(int(area[0]), int(area[1]),
|
|
int(area[2]), int(area[3]))
|
|
except:
|
|
# No, not a rect, assume a seq of rects.
|
|
pass
|
|
if not varea: # len(area) == 4 AND varea set.
|
|
varea = []
|
|
for r in area:
|
|
varea.append(rect.SDL_Rect(r[0], r[1], r[2], r[3]))
|
|
|
|
if varea is None or isinstance(varea, rect.SDL_Rect):
|
|
surface.SDL_FillRect(rtarget, varea, color)
|
|
else:
|
|
varea, count = to_ctypes(varea, rect.SDL_Rect)
|
|
varea = ctypes.cast(varea, ctypes.POINTER(rect.SDL_Rect))
|
|
surface.SDL_FillRects(rtarget, varea, count, color)
|
|
|
|
|
|
def line(target, color, dline, width=1):
|
|
"""Draws one or multiple lines on the passed target.
|
|
|
|
dline can be a sequence of four integers for a single line in the
|
|
form (x1, y1, x2, y2) or a sequence of a multiple of 4 for drawing
|
|
multiple lines at once, e.g. (x1, y1, x2, y2, x3, y3, x4, y4, ...).
|
|
"""
|
|
if width < 1:
|
|
raise ValueError("width must be greater than 0")
|
|
color = prepare_color(color, target)
|
|
rtarget = _get_target_surface(target)
|
|
|
|
# line: (x1, y1, x2, y2) OR (x1, y1, x2, y2, ...)
|
|
if (len(dline) % 4) != 0:
|
|
raise ValueError("line does not contain a valid set of points")
|
|
pcount = len(dline)
|
|
SDLRect = rect.SDL_Rect
|
|
fillrect = surface.SDL_FillRect
|
|
|
|
pitch = rtarget.pitch
|
|
bpp = rtarget.format.contents.BytesPerPixel
|
|
frac = pitch / bpp
|
|
clip_rect = rtarget.clip_rect
|
|
left, right = clip_rect.x, clip_rect.x + clip_rect.w - 1
|
|
top, bottom = clip_rect.y, clip_rect.y + clip_rect.h - 1
|
|
|
|
if bpp == 3:
|
|
raise UnsupportedError(line, "24bpp are currently not supported")
|
|
if bpp == 2:
|
|
pxbuf = ctypes.cast(rtarget.pixels, ctypes.POINTER(ctypes.c_uint16))
|
|
elif bpp == 4:
|
|
pxbuf = ctypes.cast(rtarget.pixels, ctypes.POINTER(ctypes.c_uint32))
|
|
else:
|
|
pxbuf = rtarget.pixels # byte-wise access.
|
|
|
|
for idx in range(0, pcount, 4):
|
|
x1, y1, x2, y2 = dline[idx:idx + 4]
|
|
if x1 == x2:
|
|
# Vertical line
|
|
if y1 < y2:
|
|
varea = SDLRect(x1 - width // 2, y1, width, y2 - y1)
|
|
else:
|
|
varea = SDLRect(x1 - width // 2, y2, width, y1 - y2)
|
|
fillrect(rtarget, varea, color)
|
|
continue
|
|
if y1 == y2:
|
|
# Horizontal line
|
|
if x1 < x2:
|
|
varea = SDLRect(x1, y1 - width // 2, x2 - x1, width)
|
|
else:
|
|
varea = SDLRect(x2, y1 - width // 2, x1 - x2, width)
|
|
fillrect(rtarget, varea, color)
|
|
continue
|
|
if width != 1:
|
|
raise UnsupportedError(line, "width > 1 is not supported")
|
|
if width == 1:
|
|
# Bresenham
|
|
x1, y1, x2, y2 = clipline(left, top, right, bottom, x1, y1, x2, y2)
|
|
x1, y1, x2, y2 = int(x1), int(y1), int(x2), int(y2)
|
|
if x1 is None:
|
|
# not to be drawn
|
|
continue
|
|
dx = abs(x2 - x1)
|
|
dy = -abs(y2 - y1)
|
|
err = dx + dy
|
|
sx, sy = 1, 1
|
|
if x1 > x2:
|
|
sx = -sx
|
|
if y1 > y2:
|
|
sy = -sy
|
|
while True:
|
|
pxbuf[int(y1 * frac + x1)] = color
|
|
if x1 == x2 and y1 == y2:
|
|
break
|
|
e2 = err * 2
|
|
if e2 > dy:
|
|
err += dy
|
|
x1 += sx
|
|
if e2 < dx:
|
|
err += dx
|
|
y1 += sy
|